From 148a987a23aacf4ff6d7237714de390381ed80e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Thu, 23 Sep 2021 17:36:13 +0200 Subject: [PATCH] Update the code base to use file-scoped namespaces --- .../Controllers/AuthenticationController.cs | 35 +- .../Mvc.Client/Controllers/HomeController.cs | 47 +- samples/Mvc.Client/Program.cs | 17 +- samples/Mvc.Client/Startup.cs | 121 +- .../Controllers/AccountController.cs | 749 +- .../Controllers/AuthorizationController.cs | 843 ++- .../Mvc.Server/Controllers/ErrorController.cs | 35 +- .../Mvc.Server/Controllers/HomeController.cs | 39 +- .../Controllers/ManageController.cs | 543 +- .../Controllers/ResourceController.cs | 79 +- .../Controllers/UserinfoController.cs | 89 +- .../Helpers/AsyncEnumerableExtensions.cs | 33 +- .../Helpers/FormValueRequiredAttribute.cs | 47 +- .../Mvc.Server/Models/ApplicationDbContext.cs | 23 +- samples/Mvc.Server/Models/ApplicationUser.cs | 9 +- samples/Mvc.Server/Program.cs | 17 +- samples/Mvc.Server/Services/IEmailSender.cs | 9 +- samples/Mvc.Server/Services/ISmsSender.cs | 9 +- .../Mvc.Server/Services/MessageServices.cs | 29 +- samples/Mvc.Server/Startup.cs | 341 +- .../ExternalLoginConfirmationViewModel.cs | 13 +- .../Account/ForgotPasswordViewModel.cs | 13 +- .../ViewModels/Account/LoginViewModel.cs | 23 +- .../ViewModels/Account/RegisterViewModel.cs | 33 +- .../Account/ResetPasswordViewModel.cs | 31 +- .../ViewModels/Account/SendCodeViewModel.cs | 15 +- .../ViewModels/Account/VerifyCodeViewModel.cs | 25 +- .../Authorization/AuthorizeViewModel.cs | 15 +- .../Authorization/VerifyViewModel.cs | 29 +- .../Manage/AddPhoneNumberViewModel.cs | 15 +- .../Manage/ChangePasswordViewModel.cs | 33 +- .../Manage/ConfigureTwoFactorViewModel.cs | 11 +- .../ViewModels/Manage/FactorViewModel.cs | 9 +- .../ViewModels/Manage/IndexViewModel.cs | 17 +- .../Manage/ManageLoginsViewModel.cs | 11 +- .../ViewModels/Manage/RemoveLoginViewModel.cs | 13 +- .../ViewModels/Manage/SetPasswordViewModel.cs | 25 +- .../Manage/VerifyPhoneNumberViewModel.cs | 19 +- .../ViewModels/Shared/ErrorViewModel.cs | 15 +- samples/Mvc.Server/Worker.cs | 211 +- .../Helpers/OpenIddictHelpers.cs | 101 +- .../Caches/IOpenIddictApplicationCache.cs | 107 +- .../Caches/IOpenIddictAuthorizationCache.cs | 173 +- .../Caches/IOpenIddictScopeCache.cs | 107 +- .../Caches/IOpenIddictTokenCache.cs | 183 +- .../OpenIddictApplicationDescriptor.cs | 101 +- .../OpenIddictAuthorizationDescriptor.cs | 91 +- .../Descriptors/OpenIddictScopeDescriptor.cs | 77 +- .../Descriptors/OpenIddictTokenDescriptor.cs | 111 +- .../Managers/IOpenIddictApplicationManager.cs | 901 ++- .../IOpenIddictAuthorizationManager.cs | 811 ++- .../Managers/IOpenIddictScopeManager.cs | 725 +- .../Managers/IOpenIddictTokenManager.cs | 907 ++- .../OpenIddictBuilder.cs | 49 +- .../OpenIddictConstants.cs | 841 ++- .../OpenIddictExceptions.cs | 225 +- .../OpenIddictExtensions.cs | 71 +- .../Primitives/OpenIddictConverter.cs | 103 +- .../Primitives/OpenIddictExtensions.cs | 2809 ++++---- .../Primitives/OpenIddictMessage.cs | 611 +- .../Primitives/OpenIddictParameter.cs | 1335 ++-- .../Primitives/OpenIddictRequest.cs | 875 ++- .../Primitives/OpenIddictResponse.cs | 371 +- .../IOpenIddictApplicationStoreResolver.cs | 23 +- .../IOpenIddictAuthorizationStoreResolver.cs | 23 +- .../IOpenIddictScopeStoreResolver.cs | 23 +- .../IOpenIddictTokenStoreResolver.cs | 23 +- .../Stores/IOpenIddictApplicationStore.cs | 755 +- .../Stores/IOpenIddictAuthorizationStore.cs | 687 +- .../Stores/IOpenIddictScopeStore.cs | 529 +- .../Stores/IOpenIddictTokenStore.cs | 855 ++- .../Caches/OpenIddictApplicationCache.cs | 507 +- .../Caches/OpenIddictAuthorizationCache.cs | 773 +- .../Caches/OpenIddictScopeCache.cs | 469 +- .../Caches/OpenIddictTokenCache.cs | 845 ++- .../Managers/OpenIddictApplicationManager.cs | 2653 ++++--- .../OpenIddictAuthorizationManager.cs | 2113 +++--- .../Managers/OpenIddictScopeManager.cs | 1723 +++-- .../Managers/OpenIddictTokenManager.cs | 2415 ++++--- src/OpenIddict.Core/OpenIddictCoreBuilder.cs | 1371 ++-- .../OpenIddictCoreExtensions.cs | 177 +- src/OpenIddict.Core/OpenIddictCoreOptions.cs | 95 +- .../OpenIddictApplicationStoreResolver.cs | 37 +- .../OpenIddictAuthorizationStoreResolver.cs | 37 +- .../Resolvers/OpenIddictScopeStoreResolver.cs | 37 +- .../Resolvers/OpenIddictTokenStoreResolver.cs | 37 +- .../OpenIddictEntityFrameworkApplication.cs | 203 +- .../OpenIddictEntityFrameworkAuthorization.cs | 119 +- .../OpenIddictEntityFrameworkScope.cs | 111 +- .../OpenIddictEntityFrameworkToken.cs | 175 +- ...EntityFrameworkApplicationConfiguration.cs | 97 +- ...tityFrameworkAuthorizationConfiguration.cs | 71 +- ...IddictEntityFrameworkScopeConfiguration.cs | 53 +- ...IddictEntityFrameworkTokenConfiguration.cs | 73 +- .../OpenIddictEntityFrameworkBuilder.cs | 185 +- .../OpenIddictEntityFrameworkExtensions.cs | 117 +- .../OpenIddictEntityFrameworkHelpers.cs | 121 +- .../OpenIddictEntityFrameworkOptions.cs | 23 +- ...EntityFrameworkApplicationStoreResolver.cs | 101 +- ...tityFrameworkAuthorizationStoreResolver.cs | 101 +- ...IddictEntityFrameworkScopeStoreResolver.cs | 97 +- ...IddictEntityFrameworkTokenStoreResolver.cs | 101 +- ...enIddictEntityFrameworkApplicationStore.cs | 1477 ++-- ...IddictEntityFrameworkAuthorizationStore.cs | 1313 ++-- .../OpenIddictEntityFrameworkScopeStore.cs | 1033 ++- .../OpenIddictEntityFrameworkTokenStore.cs | 1347 ++-- ...penIddictEntityFrameworkCoreApplication.cs | 213 +- ...nIddictEntityFrameworkCoreAuthorization.cs | 131 +- .../OpenIddictEntityFrameworkCoreScope.cs | 111 +- .../OpenIddictEntityFrameworkCoreToken.cs | 185 +- ...tyFrameworkCoreApplicationConfiguration.cs | 99 +- ...FrameworkCoreAuthorizationConfiguration.cs | 91 +- ...ctEntityFrameworkCoreScopeConfiguration.cs | 65 +- ...ctEntityFrameworkCoreTokenConfiguration.cs | 95 +- .../OpenIddictEntityFrameworkCoreBuilder.cs | 195 +- ...OpenIddictEntityFrameworkCoreCustomizer.cs | 55 +- ...OpenIddictEntityFrameworkCoreExtensions.cs | 117 +- .../OpenIddictEntityFrameworkCoreHelpers.cs | 277 +- .../OpenIddictEntityFrameworkCoreOptions.cs | 23 +- ...tyFrameworkCoreApplicationStoreResolver.cs | 101 +- ...FrameworkCoreAuthorizationStoreResolver.cs | 101 +- ...ctEntityFrameworkCoreScopeStoreResolver.cs | 97 +- ...ctEntityFrameworkCoreTokenStoreResolver.cs | 101 +- ...dictEntityFrameworkCoreApplicationStore.cs | 1551 ++-- ...ctEntityFrameworkCoreAuthorizationStore.cs | 1467 ++-- ...OpenIddictEntityFrameworkCoreScopeStore.cs | 1069 ++- ...OpenIddictEntityFrameworkCoreTokenStore.cs | 1475 ++-- .../OpenIddictMongoDbApplication.cs | 151 +- .../OpenIddictMongoDbAuthorization.cs | 99 +- .../OpenIddictMongoDbScope.cs | 105 +- .../OpenIddictMongoDbToken.cs | 151 +- .../IOpenIddictMongoDbContext.cs | 27 +- .../OpenIddictMongoDbBuilder.cs | 281 +- .../OpenIddictMongoDbContext.cs | 57 +- .../OpenIddictMongoDbExtensions.cs | 111 +- .../OpenIddictMongoDbHelpers.cs | 75 +- .../OpenIddictMongoDbOptions.cs | 51 +- ...enIddictMongoDbApplicationStoreResolver.cs | 59 +- ...IddictMongoDbAuthorizationStoreResolver.cs | 59 +- .../OpenIddictMongoDbScopeStoreResolver.cs | 59 +- .../OpenIddictMongoDbTokenStoreResolver.cs | 59 +- .../OpenIddictMongoDbApplicationStore.cs | 931 ++- .../OpenIddictMongoDbAuthorizationStore.cs | 1037 ++- .../Stores/OpenIddictMongoDbScopeStore.cs | 717 +- .../Stores/OpenIddictMongoDbTokenStore.cs | 1147 ++- .../OpenIddictQuartzBuilder.cs | 179 +- .../OpenIddictQuartzConfiguration.cs | 55 +- .../OpenIddictQuartzExtensions.cs | 85 +- src/OpenIddict.Quartz/OpenIddictQuartzJob.cs | 305 +- .../OpenIddictQuartzOptions.cs | 55 +- .../OpenIddictServerAspNetCoreBuilder.cs | 353 +- ...OpenIddictServerAspNetCoreConfiguration.cs | 145 +- .../OpenIddictServerAspNetCoreConstants.cs | 57 +- .../OpenIddictServerAspNetCoreDefaults.cs | 17 +- .../OpenIddictServerAspNetCoreExtensions.cs | 123 +- .../OpenIddictServerAspNetCoreFeature.cs | 19 +- .../OpenIddictServerAspNetCoreHandler.cs | 567 +- ...penIddictServerAspNetCoreHandlerFilters.cs | 339 +- ...ServerAspNetCoreHandlers.Authentication.cs | 849 ++- ...enIddictServerAspNetCoreHandlers.Device.cs | 157 +- ...ddictServerAspNetCoreHandlers.Discovery.cs | 51 +- ...IddictServerAspNetCoreHandlers.Exchange.cs | 43 +- ...tServerAspNetCoreHandlers.Introspection.cs | 33 +- ...dictServerAspNetCoreHandlers.Revocation.cs | 35 +- ...nIddictServerAspNetCoreHandlers.Session.cs | 653 +- ...IddictServerAspNetCoreHandlers.Userinfo.cs | 43 +- .../OpenIddictServerAspNetCoreHandlers.cs | 2199 +++--- .../OpenIddictServerAspNetCoreHelpers.cs | 115 +- .../OpenIddictServerAspNetCoreOptions.cs | 187 +- ...OpenIddictServerDataProtectionFormatter.cs | 13 +- .../OpenIddictServerDataProtectionBuilder.cs | 203 +- ...IddictServerDataProtectionConfiguration.cs | 67 +- ...OpenIddictServerDataProtectionConstants.cs | 89 +- ...penIddictServerDataProtectionExtensions.cs | 95 +- ...OpenIddictServerDataProtectionFormatter.cs | 617 +- ...ServerDataProtectionHandlers.Protection.cs | 505 +- .../OpenIddictServerDataProtectionHandlers.cs | 13 +- .../OpenIddictServerDataProtectionOptions.cs | 97 +- .../OpenIddictServerOwinBuilder.cs | 331 +- .../OpenIddictServerOwinConfiguration.cs | 45 +- .../OpenIddictServerOwinConstants.cs | 77 +- .../OpenIddictServerOwinDefaults.cs | 17 +- .../OpenIddictServerOwinExtensions.cs | 123 +- .../OpenIddictServerOwinHandler.cs | 529 +- .../OpenIddictServerOwinHandlerFilters.cs | 307 +- ...IddictServerOwinHandlers.Authentication.cs | 825 ++- .../OpenIddictServerOwinHandlers.Device.cs | 155 +- .../OpenIddictServerOwinHandlers.Discovery.cs | 51 +- .../OpenIddictServerOwinHandlers.Exchange.cs | 43 +- ...nIddictServerOwinHandlers.Introspection.cs | 33 +- ...OpenIddictServerOwinHandlers.Revocation.cs | 35 +- .../OpenIddictServerOwinHandlers.Session.cs | 629 +- .../OpenIddictServerOwinHandlers.Userinfo.cs | 43 +- .../OpenIddictServerOwinHandlers.cs | 1895 +++-- .../OpenIddictServerOwinHelpers.cs | 143 +- .../OpenIddictServerOwinMiddleware.cs | 65 +- .../OpenIddictServerOwinMiddlewareFactory.cs | 85 +- .../OpenIddictServerOwinOptions.cs | 181 +- .../IOpenIddictServerDispatcher.cs | 11 +- .../IOpenIddictServerFactory.cs | 11 +- .../IOpenIddictServerHandler.cs | 27 +- .../IOpenIddictServerHandlerFilter.cs | 9 +- .../OpenIddictServerBuilder.cs | 3079 ++++---- .../OpenIddictServerConfiguration.cs | 503 +- .../OpenIddictServerDispatcher.cs | 181 +- .../OpenIddictServerEndpointType.cs | 97 +- .../OpenIddictServerEvents.Authentication.cs | 335 +- .../OpenIddictServerEvents.Device.cs | 445 +- .../OpenIddictServerEvents.Discovery.cs | 615 +- .../OpenIddictServerEvents.Exchange.cs | 243 +- .../OpenIddictServerEvents.Introspection.cs | 357 +- .../OpenIddictServerEvents.Protection.cs | 251 +- .../OpenIddictServerEvents.Revocation.cs | 203 +- .../OpenIddictServerEvents.Session.cs | 285 +- .../OpenIddictServerEvents.Userinfo.cs | 391 +- .../OpenIddictServerEvents.cs | 1393 ++-- .../OpenIddictServerExtensions.cs | 171 +- .../OpenIddictServerFactory.cs | 43 +- .../OpenIddictServerHandler.cs | 45 +- .../OpenIddictServerHandlerDescriptor.cs | 439 +- .../OpenIddictServerHandlerFilters.cs | 841 ++- .../OpenIddictServerHandlerType.cs | 33 +- ...OpenIddictServerHandlers.Authentication.cs | 2701 ++++--- .../OpenIddictServerHandlers.Device.cs | 1893 +++-- .../OpenIddictServerHandlers.Discovery.cs | 1971 +++-- .../OpenIddictServerHandlers.Exchange.cs | 2759 ++++--- .../OpenIddictServerHandlers.Introspection.cs | 1755 +++-- .../OpenIddictServerHandlers.Protection.cs | 2245 +++--- .../OpenIddictServerHandlers.Revocation.cs | 1461 ++-- .../OpenIddictServerHandlers.Session.cs | 763 +- .../OpenIddictServerHandlers.Userinfo.cs | 863 ++- .../OpenIddictServerHandlers.cs | 4939 +++++++------ .../OpenIddictServerHelpers.cs | 109 +- .../OpenIddictServerOptions.cs | 743 +- .../OpenIddictServerTransaction.cs | 77 +- .../OpenIddictValidationAspNetCoreBuilder.cs | 103 +- ...IddictValidationAspNetCoreConfiguration.cs | 105 +- ...OpenIddictValidationAspNetCoreConstants.cs | 35 +- .../OpenIddictValidationAspNetCoreDefaults.cs | 17 +- ...penIddictValidationAspNetCoreExtensions.cs | 101 +- .../OpenIddictValidationAspNetCoreFeature.cs | 19 +- .../OpenIddictValidationAspNetCoreHandler.cs | 325 +- ...ddictValidationAspNetCoreHandlerFilters.cs | 29 +- .../OpenIddictValidationAspNetCoreHandlers.cs | 1047 ++- .../OpenIddictValidationAspNetCoreHelpers.cs | 115 +- .../OpenIddictValidationAspNetCoreOptions.cs | 17 +- ...IddictValidationDataProtectionFormatter.cs | 11 +- ...enIddictValidationDataProtectionBuilder.cs | 129 +- ...ctValidationDataProtectionConfiguration.cs | 67 +- ...IddictValidationDataProtectionConstants.cs | 81 +- ...ddictValidationDataProtectionExtensions.cs | 91 +- ...IddictValidationDataProtectionFormatter.cs | 269 +- ...dationDataProtectionHandlers.Protection.cs | 175 +- ...nIddictValidationDataProtectionHandlers.cs | 13 +- ...enIddictValidationDataProtectionOptions.cs | 35 +- .../OpenIddictValidationOwinBuilder.cs | 131 +- .../OpenIddictValidationOwinConfiguration.cs | 25 +- .../OpenIddictValidationOwinConstants.cs | 55 +- .../OpenIddictValidationOwinDefaults.cs | 17 +- .../OpenIddictValidationOwinExtensions.cs | 101 +- .../OpenIddictValidationOwinHandler.cs | 341 +- .../OpenIddictValidationOwinHandlerFilters.cs | 27 +- .../OpenIddictValidationOwinHandlers.cs | 1033 ++- .../OpenIddictValidationOwinHelpers.cs | 143 +- .../OpenIddictValidationOwinMiddleware.cs | 65 +- ...enIddictValidationOwinMiddlewareFactory.cs | 85 +- .../OpenIddictValidationOwinOptions.cs | 29 +- ...ddictValidationServerIntegrationBuilder.cs | 79 +- ...alidationServerIntegrationConfiguration.cs | 133 +- ...ctValidationServerIntegrationExtensions.cs | 87 +- ...ddictValidationServerIntegrationOptions.cs | 13 +- ...penIddictValidationSystemNetHttpBuilder.cs | 91 +- ...ictValidationSystemNetHttpConfiguration.cs | 93 +- ...IddictValidationSystemNetHttpExtensions.cs | 95 +- ...ctValidationSystemNetHttpHandlerFilters.cs | 31 +- ...lidationSystemNetHttpHandlers.Discovery.cs | 59 +- ...tionSystemNetHttpHandlers.Introspection.cs | 157 +- ...enIddictValidationSystemNetHttpHandlers.cs | 597 +- ...penIddictValidationSystemNetHttpHelpers.cs | 37 +- ...penIddictValidationSystemNetHttpOptions.cs | 23 +- .../IOpenIddictValidationDispatcher.cs | 11 +- .../IOpenIddictValidationFactory.cs | 11 +- .../IOpenIddictValidationHandler.cs | 27 +- .../IOpenIddictValidationHandlerFilter.cs | 9 +- .../OpenIddictValidationBuilder.cs | 783 +- .../OpenIddictValidationConfiguration.cs | 199 +- .../OpenIddictValidationDispatcher.cs | 181 +- .../OpenIddictValidationEndpointType.cs | 17 +- .../OpenIddictValidationEvents.Discovery.cs | 469 +- ...penIddictValidationEvents.Introspection.cs | 229 +- .../OpenIddictValidationEvents.Protection.cs | 89 +- .../OpenIddictValidationEvents.cs | 537 +- .../OpenIddictValidationExtensions.cs | 111 +- .../OpenIddictValidationFactory.cs | 43 +- .../OpenIddictValidationHandler.cs | 45 +- .../OpenIddictValidationHandlerDescriptor.cs | 439 +- .../OpenIddictValidationHandlerFilters.cs | 115 +- .../OpenIddictValidationHandlerType.cs | 33 +- .../OpenIddictValidationHandlers.Discovery.cs | 485 +- ...nIddictValidationHandlers.Introspection.cs | 705 +- ...OpenIddictValidationHandlers.Protection.cs | 1351 ++-- .../OpenIddictValidationHandlers.cs | 443 +- .../OpenIddictValidationHelpers.cs | 109 +- .../OpenIddictValidationOptions.cs | 281 +- .../OpenIddictValidationRetriever.cs | 83 +- .../OpenIddictValidationService.cs | 779 +- .../OpenIddictValidationTransaction.cs | 77 +- .../OpenIddictValidationType.cs | 35 +- .../OpenIddictBuilderTests.cs | 21 +- .../OpenIddictExtensionsTests.cs | 39 +- .../Primitives/OpenIddictConverterTests.cs | 483 +- .../Primitives/OpenIddictExtensionsTests.cs | 4963 +++++++------ .../Primitives/OpenIddictMessageTests.cs | 793 +- .../Primitives/OpenIddictParameterTests.cs | 2088 +++--- .../Primitives/OpenIddictRequestTests.cs | 625 +- .../Primitives/OpenIddictResponseTests.cs | 231 +- .../OpenIddictCoreBuilderTests.cs | 1247 ++-- .../OpenIddictCoreExtensionsTests.cs | 485 +- ...OpenIddictApplicationStoreResolverTests.cs | 61 +- ...enIddictAuthorizationStoreResolverTests.cs | 61 +- .../OpenIddictScopeStoreResolverTests.cs | 61 +- .../OpenIddictTokenStoreResolverTests.cs | 61 +- .../OpenIddictEntityFrameworkBuilderTests.cs | 193 +- ...penIddictEntityFrameworkExtensionsTests.cs | 203 +- ...yFrameworkApplicationStoreResolverTests.cs | 173 +- ...rameworkAuthorizationStoreResolverTests.cs | 173 +- ...tEntityFrameworkScopeStoreResolverTests.cs | 173 +- ...tEntityFrameworkTokenStoreResolverTests.cs | 173 +- ...enIddictEntityFrameworkCoreBuilderTests.cs | 199 +- ...ddictEntityFrameworkCoreExtensionsTests.cs | 203 +- ...enIddictEntityFrameworkCoreHelpersTests.cs | 163 +- ...meworkCoreApplicationStoreResolverTests.cs | 173 +- ...workCoreAuthorizationStoreResolverTests.cs | 173 +- ...ityFrameworkCoreScopeStoreResolverTests.cs | 173 +- ...ityFrameworkCoreTokenStoreResolverTests.cs | 173 +- .../OpenIddictMongoDbBuilderTests.cs | 413 +- .../OpenIddictMongoDbContextTests.cs | 139 +- .../OpenIddictMongoDbExtensionsTests.cs | 197 +- ...ictMongoDbApplicationStoreResolverTests.cs | 85 +- ...tMongoDbAuthorizationStoreResolverTests.cs | 85 +- ...penIddictMongoDbScopeStoreResolverTests.cs | 85 +- ...penIddictMongoDbTokenStoreResolverTests.cs | 85 +- .../OpenIddictQuartzBuilderTests.cs | 353 +- .../OpenIddictQuartzConfigurationTests.cs | 47 +- .../OpenIddictQuartzExtensionsTests.cs | 103 +- .../OpenIddictQuartzJobTests.cs | 511 +- ...ctServerAspNetCoreIntegrationTestServer.cs | 75 +- ...pNetCoreIntegrationTests.Authentication.cs | 83 +- ...rverAspNetCoreIntegrationTests.Exchange.cs | 63 +- ...spNetCoreIntegrationTests.Introspection.cs | 59 +- ...erAspNetCoreIntegrationTests.Revocation.cs | 59 +- ...erverAspNetCoreIntegrationTests.Session.cs | 83 +- ...nIddictServerAspNetCoreIntegrationTests.cs | 1627 +++-- .../OpenIddictServerIntegrationTestClient.cs | 747 +- .../OpenIddictServerIntegrationTestServer.cs | 19 +- ...ctServerIntegrationTests.Authentication.cs | 3859 +++++----- ...OpenIddictServerIntegrationTests.Device.cs | 2235 +++--- ...nIddictServerIntegrationTests.Discovery.cs | 2339 +++--- ...enIddictServerIntegrationTests.Exchange.cs | 6411 ++++++++--------- ...ictServerIntegrationTests.Introspection.cs | 2537 ++++--- ...IddictServerIntegrationTests.Protection.cs | 793 +- ...IddictServerIntegrationTests.Revocation.cs | 1589 ++-- ...penIddictServerIntegrationTests.Session.cs | 1281 ++-- ...enIddictServerIntegrationTests.Userinfo.cs | 1215 ++-- .../OpenIddictServerIntegrationTests.cs | 5059 +++++++------ ...enIddictServerOwinIntegrationTestServer.cs | 45 +- ...rverOwinIntegrationTests.Authentication.cs | 83 +- ...dictServerOwinIntegrationTests.Exchange.cs | 63 +- ...erverOwinIntegrationTests.Introspection.cs | 59 +- ...ctServerOwinIntegrationTests.Revocation.cs | 59 +- ...ddictServerOwinIntegrationTests.Session.cs | 83 +- .../OpenIddictServerOwinIntegrationTests.cs | 1365 ++-- .../OpenIddictServerBuilderTests.cs | 3283 +++++---- .../OpenIddictServerExtensionsTests.cs | 383 +- 374 files changed, 81217 insertions(+), 81590 deletions(-) diff --git a/samples/Mvc.Client/Controllers/AuthenticationController.cs b/samples/Mvc.Client/Controllers/AuthenticationController.cs index 0ffcc266..f6f18f48 100644 --- a/samples/Mvc.Client/Controllers/AuthenticationController.cs +++ b/samples/Mvc.Client/Controllers/AuthenticationController.cs @@ -3,25 +3,24 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Mvc; -namespace Mvc.Client.Controllers +namespace Mvc.Client.Controllers; + +public class AuthenticationController : Controller { - public class AuthenticationController : Controller + [HttpGet("~/login")] + public ActionResult LogIn() { - [HttpGet("~/login")] - public ActionResult LogIn() - { - // Instruct the OIDC client middleware to redirect the user agent to the identity provider. - // Note: the authenticationType parameter must match the value configured in Startup.cs - return Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectDefaults.AuthenticationScheme); - } + // Instruct the OIDC client middleware to redirect the user agent to the identity provider. + // Note: the authenticationType parameter must match the value configured in Startup.cs + return Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectDefaults.AuthenticationScheme); + } - [HttpGet("~/logout"), HttpPost("~/logout")] - public ActionResult LogOut() - { - // Instruct the cookies middleware to delete the local cookie created when the user agent - // is redirected from the identity provider after a successful authorization flow and - // to redirect the user agent to the identity provider to sign out. - return SignOut(CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme); - } + [HttpGet("~/logout"), HttpPost("~/logout")] + public ActionResult LogOut() + { + // Instruct the cookies middleware to delete the local cookie created when the user agent + // is redirected from the identity provider after a successful authorization flow and + // to redirect the user agent to the identity provider to sign out. + return SignOut(CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme); } -} \ No newline at end of file +} diff --git a/samples/Mvc.Client/Controllers/HomeController.cs b/samples/Mvc.Client/Controllers/HomeController.cs index ca426153..1941076b 100644 --- a/samples/Mvc.Client/Controllers/HomeController.cs +++ b/samples/Mvc.Client/Controllers/HomeController.cs @@ -9,37 +9,36 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -namespace Mvc.Client.Controllers +namespace Mvc.Client.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller - { - private readonly IHttpClientFactory _httpClientFactory; + private readonly IHttpClientFactory _httpClientFactory; - public HomeController(IHttpClientFactory httpClientFactory) - => _httpClientFactory = httpClientFactory; + public HomeController(IHttpClientFactory httpClientFactory) + => _httpClientFactory = httpClientFactory; - [HttpGet("~/")] - public ActionResult Index() => View("Home"); + [HttpGet("~/")] + public ActionResult Index() => View("Home"); - [Authorize, HttpPost("~/")] - public async Task Index(CancellationToken cancellationToken) + [Authorize, HttpPost("~/")] + public async Task Index(CancellationToken cancellationToken) + { + var token = await HttpContext.GetTokenAsync(CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectParameterNames.AccessToken); + if (string.IsNullOrEmpty(token)) { - var token = await HttpContext.GetTokenAsync(CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectParameterNames.AccessToken); - if (string.IsNullOrEmpty(token)) - { - throw new InvalidOperationException("The access token cannot be found in the authentication ticket. " + - "Make sure that SaveTokens is set to true in the OIDC options."); - } + throw new InvalidOperationException("The access token cannot be found in the authentication ticket. " + + "Make sure that SaveTokens is set to true in the OIDC options."); + } - using var client = _httpClientFactory.CreateClient(); + using var client = _httpClientFactory.CreateClient(); - using var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44395/api/message"); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + using var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44395/api/message"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - using var response = await client.SendAsync(request, cancellationToken); - response.EnsureSuccessStatusCode(); + using var response = await client.SendAsync(request, cancellationToken); + response.EnsureSuccessStatusCode(); - return View("Home", model: await response.Content.ReadAsStringAsync(cancellationToken)); - } + return View("Home", model: await response.Content.ReadAsStringAsync(cancellationToken)); } -} \ No newline at end of file +} diff --git a/samples/Mvc.Client/Program.cs b/samples/Mvc.Client/Program.cs index a15fd7dc..a04fccd4 100644 --- a/samples/Mvc.Client/Program.cs +++ b/samples/Mvc.Client/Program.cs @@ -1,15 +1,14 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -namespace Mvc.Client +namespace Mvc.Client; + +public static class Program { - public static class Program - { - public static void Main(string[] args) => - CreateHostBuilder(args).Build().Run(); + public static void Main(string[] args) => + CreateHostBuilder(args).Build().Run(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(builder => builder.UseStartup()); - } + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(builder => builder.UseStartup()); } diff --git a/samples/Mvc.Client/Startup.cs b/samples/Mvc.Client/Startup.cs index 8efe9d2d..3860f424 100644 --- a/samples/Mvc.Client/Startup.cs +++ b/samples/Mvc.Client/Startup.cs @@ -7,80 +7,79 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -namespace Mvc.Client +namespace Mvc.Client; + +public class Startup { - public class Startup + public void ConfigureServices(IServiceCollection services) { - public void ConfigureServices(IServiceCollection services) + services.AddAuthentication(options => { - services.AddAuthentication(options => - { - options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }) + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + }) - .AddCookie(options => - { - options.LoginPath = "/login"; - options.ExpireTimeSpan = TimeSpan.FromMinutes(50); - options.SlidingExpiration = false; - }) + .AddCookie(options => + { + options.LoginPath = "/login"; + options.ExpireTimeSpan = TimeSpan.FromMinutes(50); + options.SlidingExpiration = false; + }) - .AddOpenIdConnect(options => + .AddOpenIdConnect(options => + { + // Note: these settings must match the application details + // inserted in the database at the server level. + options.ClientId = "mvc"; + options.ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654"; + + options.RequireHttpsMetadata = false; + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + + // Use the authorization code flow. + options.ResponseType = OpenIdConnectResponseType.Code; + options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet; + + // Note: setting the Authority allows the OIDC client middleware to automatically + // retrieve the identity provider's configuration and spare you from setting + // the different endpoints URIs or the token validation parameters explicitly. + options.Authority = "https://localhost:44395/"; + + options.Scope.Add("email"); + options.Scope.Add("roles"); + options.Scope.Add("offline_access"); + options.Scope.Add("demo_api"); + + options.SecurityTokenValidator = new JwtSecurityTokenHandler { - // Note: these settings must match the application details - // inserted in the database at the server level. - options.ClientId = "mvc"; - options.ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654"; - - options.RequireHttpsMetadata = false; - options.GetClaimsFromUserInfoEndpoint = true; - options.SaveTokens = true; - - // Use the authorization code flow. - options.ResponseType = OpenIdConnectResponseType.Code; - options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet; + // Disable the built-in JWT claims mapping feature. + InboundClaimTypeMap = new Dictionary() + }; - // Note: setting the Authority allows the OIDC client middleware to automatically - // retrieve the identity provider's configuration and spare you from setting - // the different endpoints URIs or the token validation parameters explicitly. - options.Authority = "https://localhost:44395/"; + options.TokenValidationParameters.NameClaimType = "name"; + options.TokenValidationParameters.RoleClaimType = "role"; - options.Scope.Add("email"); - options.Scope.Add("roles"); - options.Scope.Add("offline_access"); - options.Scope.Add("demo_api"); + options.AccessDeniedPath = "/"; + }); - options.SecurityTokenValidator = new JwtSecurityTokenHandler - { - // Disable the built-in JWT claims mapping feature. - InboundClaimTypeMap = new Dictionary() - }; + services.AddHttpClient(); - options.TokenValidationParameters.NameClaimType = "name"; - options.TokenValidationParameters.RoleClaimType = "role"; - - options.AccessDeniedPath = "/"; - }); - - services.AddHttpClient(); - - services.AddControllersWithViews(); - } + services.AddControllersWithViews(); + } - public void Configure(IApplicationBuilder app) - { - app.UseDeveloperExceptionPage(); + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); - app.UseStaticFiles(); + app.UseStaticFiles(); - app.UseRouting(); + app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); + app.UseAuthentication(); + app.UseAuthorization(); - app.UseEndpoints(options => options.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}")); - } + app.UseEndpoints(options => options.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}")); } -} \ No newline at end of file +} diff --git a/samples/Mvc.Server/Controllers/AccountController.cs b/samples/Mvc.Server/Controllers/AccountController.cs index 6b79b210..efc7c9e3 100644 --- a/samples/Mvc.Server/Controllers/AccountController.cs +++ b/samples/Mvc.Server/Controllers/AccountController.cs @@ -9,463 +9,462 @@ using Mvc.Server.Models; using Mvc.Server.Services; using Mvc.Server.ViewModels.Account; -namespace Mvc.Server.Controllers +namespace Mvc.Server.Controllers; + +[Authorize] +public class AccountController : Controller { - [Authorize] - public class AccountController : Controller + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; + private readonly ISmsSender _smsSender; + private readonly ApplicationDbContext _applicationDbContext; + private static bool _databaseChecked; + + public AccountController( + UserManager userManager, + SignInManager signInManager, + IEmailSender emailSender, + ISmsSender smsSender, + ApplicationDbContext applicationDbContext) { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly IEmailSender _emailSender; - private readonly ISmsSender _smsSender; - private readonly ApplicationDbContext _applicationDbContext; - private static bool _databaseChecked; - - public AccountController( - UserManager userManager, - SignInManager signInManager, - IEmailSender emailSender, - ISmsSender smsSender, - ApplicationDbContext applicationDbContext) - { - _userManager = userManager; - _signInManager = signInManager; - _emailSender = emailSender; - _smsSender = smsSender; - _applicationDbContext = applicationDbContext; - } + _userManager = userManager; + _signInManager = signInManager; + _emailSender = emailSender; + _smsSender = smsSender; + _applicationDbContext = applicationDbContext; + } - // - // GET: /Account/Login - [HttpGet] - [AllowAnonymous] - public IActionResult Login(string returnUrl = null) - { - ViewData["ReturnUrl"] = returnUrl; - return View(); - } + // + // GET: /Account/Login + [HttpGet] + [AllowAnonymous] + public IActionResult Login(string returnUrl = null) + { + ViewData["ReturnUrl"] = returnUrl; + return View(); + } - // - // POST: /Account/Login - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Login(LoginViewModel model, string returnUrl = null) + // + // POST: /Account/Login + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Login(LoginViewModel model, string returnUrl = null) + { + EnsureDatabaseCreated(_applicationDbContext); + ViewData["ReturnUrl"] = returnUrl; + if (ModelState.IsValid) { - EnsureDatabaseCreated(_applicationDbContext); - ViewData["ReturnUrl"] = returnUrl; - if (ModelState.IsValid) + // This doesn't count login failures towards account lockout + // To enable password failures to trigger account lockout, set lockoutOnFailure: true + var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); + if (result.Succeeded) { - // This doesn't count login failures towards account lockout - // To enable password failures to trigger account lockout, set lockoutOnFailure: true - var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); - if (result.Succeeded) - { - return RedirectToLocal(returnUrl); - } - if (result.RequiresTwoFactor) - { - return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); - } - if (result.IsLockedOut) - { - return View("Lockout"); - } - else - { - ModelState.AddModelError(string.Empty, "Invalid login attempt."); - return View(model); - } + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + ModelState.AddModelError(string.Empty, "Invalid login attempt."); + return View(model); } - - // If we got this far, something failed, redisplay form - return View(model); } - // - // GET: /Account/Register - [HttpGet] - [AllowAnonymous] - public IActionResult Register(string returnUrl = null) - { - ViewData["ReturnUrl"] = returnUrl; - return View(); - } + // If we got this far, something failed, redisplay form + return View(model); + } - // - // POST: /Account/Register - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Register(RegisterViewModel model, string returnUrl = null) + // + // GET: /Account/Register + [HttpGet] + [AllowAnonymous] + public IActionResult Register(string returnUrl = null) + { + ViewData["ReturnUrl"] = returnUrl; + return View(); + } + + // + // POST: /Account/Register + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Register(RegisterViewModel model, string returnUrl = null) + { + EnsureDatabaseCreated(_applicationDbContext); + ViewData["ReturnUrl"] = returnUrl; + if (ModelState.IsValid) { - EnsureDatabaseCreated(_applicationDbContext); - ViewData["ReturnUrl"] = returnUrl; - if (ModelState.IsValid) + var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + var result = await _userManager.CreateAsync(user, model.Password); + if (result.Succeeded) { - var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; - var result = await _userManager.CreateAsync(user, model.Password); - if (result.Succeeded) - { - // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713 - // Send an email with this link - //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); - //var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Context.Request.Scheme); - //await _emailSender.SendEmailAsync(model.Email, "Confirm your account", - // "Please confirm your account by clicking this link: link"); - await _signInManager.SignInAsync(user, isPersistent: false); - return RedirectToLocal(returnUrl); - } - AddErrors(result); + // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713 + // Send an email with this link + //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + //var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Context.Request.Scheme); + //await _emailSender.SendEmailAsync(model.Email, "Confirm your account", + // "Please confirm your account by clicking this link: link"); + await _signInManager.SignInAsync(user, isPersistent: false); + return RedirectToLocal(returnUrl); } - - // If we got this far, something failed, redisplay form - return View(model); + AddErrors(result); } - // - // POST: /Account/LogOff - [HttpPost] - [ValidateAntiForgeryToken] - public async Task LogOff() + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // POST: /Account/LogOff + [HttpPost] + [ValidateAntiForgeryToken] + public async Task LogOff() + { + await _signInManager.SignOutAsync(); + return RedirectToAction(nameof(HomeController.Index), "Home"); + } + + // + // POST: /Account/ExternalLogin + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public IActionResult ExternalLogin(string provider, string returnUrl = null) + { + EnsureDatabaseCreated(_applicationDbContext); + // Request a redirect to the external login provider. + var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + return new ChallengeResult(provider, properties); + } + + // + // GET: /Account/ExternalLoginCallback + [HttpGet] + [AllowAnonymous] + public async Task ExternalLoginCallback(string returnUrl = null) + { + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info is null) { - await _signInManager.SignOutAsync(); - return RedirectToAction(nameof(HomeController.Index), "Home"); + return RedirectToAction(nameof(Login)); } - // - // POST: /Account/ExternalLogin - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public IActionResult ExternalLogin(string provider, string returnUrl = null) + // Sign in the user with this external login provider if the user already has a login. + var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); + if (result.Succeeded) + { + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl }); + } + if (result.IsLockedOut) { - EnsureDatabaseCreated(_applicationDbContext); - // Request a redirect to the external login provider. - var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); - var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); - return new ChallengeResult(provider, properties); + return View("Lockout"); } + else + { + // If the user does not have an account, then ask the user to create an account. + ViewData["ReturnUrl"] = returnUrl; + ViewData["LoginProvider"] = info.LoginProvider; + var email = info.Principal.FindFirstValue(ClaimTypes.Email); + return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); + } + } - // - // GET: /Account/ExternalLoginCallback - [HttpGet] - [AllowAnonymous] - public async Task ExternalLoginCallback(string returnUrl = null) + // + // POST: /Account/ExternalLoginConfirmation + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null) + { + if (ModelState.IsValid) { + // Get the information about the user from the external login provider var info = await _signInManager.GetExternalLoginInfoAsync(); if (info is null) { - return RedirectToAction(nameof(Login)); + return View("ExternalLoginFailure"); } - - // Sign in the user with this external login provider if the user already has a login. - var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); + var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + var result = await _userManager.CreateAsync(user); if (result.Succeeded) { - return RedirectToLocal(returnUrl); - } - if (result.RequiresTwoFactor) - { - return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl }); - } - if (result.IsLockedOut) - { - return View("Lockout"); - } - else - { - // If the user does not have an account, then ask the user to create an account. - ViewData["ReturnUrl"] = returnUrl; - ViewData["LoginProvider"] = info.LoginProvider; - var email = info.Principal.FindFirstValue(ClaimTypes.Email); - return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); - } - } - - // - // POST: /Account/ExternalLoginConfirmation - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null) - { - if (ModelState.IsValid) - { - // Get the information about the user from the external login provider - var info = await _signInManager.GetExternalLoginInfoAsync(); - if (info is null) - { - return View("ExternalLoginFailure"); - } - var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; - var result = await _userManager.CreateAsync(user); + result = await _userManager.AddLoginAsync(user, info); if (result.Succeeded) { - result = await _userManager.AddLoginAsync(user, info); - if (result.Succeeded) - { - await _signInManager.SignInAsync(user, isPersistent: false); - return RedirectToLocal(returnUrl); - } + await _signInManager.SignInAsync(user, isPersistent: false); + return RedirectToLocal(returnUrl); } - AddErrors(result); } - - ViewData["ReturnUrl"] = returnUrl; - return View(model); + AddErrors(result); } - // GET: /Account/ConfirmEmail - [HttpGet] - [AllowAnonymous] - public async Task ConfirmEmail(string userId, string code) + ViewData["ReturnUrl"] = returnUrl; + return View(model); + } + + // GET: /Account/ConfirmEmail + [HttpGet] + [AllowAnonymous] + public async Task ConfirmEmail(string userId, string code) + { + if (userId is null || code is null) { - if (userId is null || code is null) - { - return View("Error"); - } - var user = await _userManager.FindByIdAsync(userId); - if (user is null) - { - return View("Error"); - } - var result = await _userManager.ConfirmEmailAsync(user, code); - return View(result.Succeeded ? "ConfirmEmail" : "Error"); + return View("Error"); } - - // - // GET: /Account/ForgotPassword - [HttpGet] - [AllowAnonymous] - public IActionResult ForgotPassword() + var user = await _userManager.FindByIdAsync(userId); + if (user is null) { - return View(); + return View("Error"); } + var result = await _userManager.ConfirmEmailAsync(user, code); + return View(result.Succeeded ? "ConfirmEmail" : "Error"); + } + + // + // GET: /Account/ForgotPassword + [HttpGet] + [AllowAnonymous] + public IActionResult ForgotPassword() + { + return View(); + } - // - // POST: /Account/ForgotPassword - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task ForgotPassword(ForgotPasswordViewModel model) + // + // POST: /Account/ForgotPassword + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ForgotPassword(ForgotPasswordViewModel model) + { + if (ModelState.IsValid) { - if (ModelState.IsValid) + var user = await _userManager.FindByNameAsync(model.Email); + if (user is null || !(await _userManager.IsEmailConfirmedAsync(user))) { - var user = await _userManager.FindByNameAsync(model.Email); - if (user is null || !(await _userManager.IsEmailConfirmedAsync(user))) - { - // Don't reveal that the user does not exist or is not confirmed - return View("ForgotPasswordConfirmation"); - } - - // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713 - // Send an email with this link - //var code = await _userManager.GeneratePasswordResetTokenAsync(user); - //var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Context.Request.Scheme); - //await _emailSender.SendEmailAsync(model.Email, "Reset Password", - // "Please reset your password by clicking here: link"); - //return View("ForgotPasswordConfirmation"); + // Don't reveal that the user does not exist or is not confirmed + return View("ForgotPasswordConfirmation"); } - // If we got this far, something failed, redisplay form - return View(model); + // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713 + // Send an email with this link + //var code = await _userManager.GeneratePasswordResetTokenAsync(user); + //var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Context.Request.Scheme); + //await _emailSender.SendEmailAsync(model.Email, "Reset Password", + // "Please reset your password by clicking here: link"); + //return View("ForgotPasswordConfirmation"); } - // - // GET: /Account/ForgotPasswordConfirmation - [HttpGet] - [AllowAnonymous] - public IActionResult ForgotPasswordConfirmation() + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // GET: /Account/ForgotPasswordConfirmation + [HttpGet] + [AllowAnonymous] + public IActionResult ForgotPasswordConfirmation() + { + return View(); + } + + // + // GET: /Account/ResetPassword + [HttpGet] + [AllowAnonymous] + public IActionResult ResetPassword(string code = null) + { + return code is null ? View("Error") : View(); + } + + // + // POST: /Account/ResetPassword + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ResetPassword(ResetPasswordViewModel model) + { + if (!ModelState.IsValid) { - return View(); + return View(model); } - - // - // GET: /Account/ResetPassword - [HttpGet] - [AllowAnonymous] - public IActionResult ResetPassword(string code = null) + var user = await _userManager.FindByNameAsync(model.Email); + if (user is null) { - return code is null ? View("Error") : View(); + // Don't reveal that the user does not exist + return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); } + var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password); + if (result.Succeeded) + { + return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); + } + AddErrors(result); + return View(); + } + + // + // GET: /Account/ResetPasswordConfirmation + [HttpGet] + [AllowAnonymous] + public IActionResult ResetPasswordConfirmation() + { + return View(); + } - // - // POST: /Account/ResetPassword - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task ResetPassword(ResetPasswordViewModel model) + // + // GET: /Account/SendCode + [HttpGet] + [AllowAnonymous] + public async Task SendCode(string returnUrl = null, bool rememberMe = false) + { + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user is null) { - if (!ModelState.IsValid) - { - return View(model); - } - var user = await _userManager.FindByNameAsync(model.Email); - if (user is null) - { - // Don't reveal that the user does not exist - return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); - } - var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password); - if (result.Succeeded) - { - return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); - } - AddErrors(result); - return View(); + return View("Error"); } + var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user); + var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); + return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } - // - // GET: /Account/ResetPasswordConfirmation - [HttpGet] - [AllowAnonymous] - public IActionResult ResetPasswordConfirmation() + // + // POST: /Account/SendCode + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task SendCode(SendCodeViewModel model) + { + if (!ModelState.IsValid) { return View(); } - // - // GET: /Account/SendCode - [HttpGet] - [AllowAnonymous] - public async Task SendCode(string returnUrl = null, bool rememberMe = false) + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user is null) { - var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); - if (user is null) - { - return View("Error"); - } - var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user); - var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); - return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); + return View("Error"); } - // - // POST: /Account/SendCode - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task SendCode(SendCodeViewModel model) + // Generate the token and send it + var code = await _userManager.GenerateTwoFactorTokenAsync(user, model.SelectedProvider); + if (string.IsNullOrWhiteSpace(code)) { - if (!ModelState.IsValid) - { - return View(); - } - - var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); - if (user is null) - { - return View("Error"); - } + return View("Error"); + } - // Generate the token and send it - var code = await _userManager.GenerateTwoFactorTokenAsync(user, model.SelectedProvider); - if (string.IsNullOrWhiteSpace(code)) - { - return View("Error"); - } + var message = "Your security code is: " + code; + if (model.SelectedProvider == "Email") + { + await _emailSender.SendEmailAsync(await _userManager.GetEmailAsync(user), "Security Code", message); + } + else if (model.SelectedProvider == "Phone") + { + await _smsSender.SendSmsAsync(await _userManager.GetPhoneNumberAsync(user), message); + } - var message = "Your security code is: " + code; - if (model.SelectedProvider == "Email") - { - await _emailSender.SendEmailAsync(await _userManager.GetEmailAsync(user), "Security Code", message); - } - else if (model.SelectedProvider == "Phone") - { - await _smsSender.SendSmsAsync(await _userManager.GetPhoneNumberAsync(user), message); - } + return RedirectToAction(nameof(VerifyCode), new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe }); + } - return RedirectToAction(nameof(VerifyCode), new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe }); + // + // GET: /Account/VerifyCode + [HttpGet] + [AllowAnonymous] + public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) + { + // Require that the user has already logged in via username/password or external login + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user is null) + { + return View("Error"); } + return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } - // - // GET: /Account/VerifyCode - [HttpGet] - [AllowAnonymous] - public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) + // + // POST: /Account/VerifyCode + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task VerifyCode(VerifyCodeViewModel model) + { + if (!ModelState.IsValid) { - // Require that the user has already logged in via username/password or external login - var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); - if (user is null) - { - return View("Error"); - } - return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + return View(model); } - // - // POST: /Account/VerifyCode - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task VerifyCode(VerifyCodeViewModel model) + // The following code protects for brute force attacks against the two factor codes. + // If a user enters incorrect codes for a specified amount of time then the user account + // will be locked out for a specified amount of time. + var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser); + if (result.Succeeded) { - if (!ModelState.IsValid) - { - return View(model); - } - - // The following code protects for brute force attacks against the two factor codes. - // If a user enters incorrect codes for a specified amount of time then the user account - // will be locked out for a specified amount of time. - var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser); - if (result.Succeeded) - { - return RedirectToLocal(model.ReturnUrl); - } - if (result.IsLockedOut) - { - return View("Lockout"); - } - else - { - ModelState.AddModelError("", "Invalid code."); - return View(model); - } + return RedirectToLocal(model.ReturnUrl); + } + if (result.IsLockedOut) + { + return View("Lockout"); } + else + { + ModelState.AddModelError("", "Invalid code."); + return View(model); + } + } - #region Helpers + #region Helpers - // The following code creates the database and schema if they don't exist. - // This is a temporary workaround since deploying database through EF migrations is - // not yet supported in this release. - // Please see this http://go.microsoft.com/fwlink/?LinkID=615859 for more information on how to do deploy the database - // when publishing your application. - private static void EnsureDatabaseCreated(ApplicationDbContext context) + // The following code creates the database and schema if they don't exist. + // This is a temporary workaround since deploying database through EF migrations is + // not yet supported in this release. + // Please see this http://go.microsoft.com/fwlink/?LinkID=615859 for more information on how to do deploy the database + // when publishing your application. + private static void EnsureDatabaseCreated(ApplicationDbContext context) + { + if (!_databaseChecked) { - if (!_databaseChecked) - { - _databaseChecked = true; - context.Database.EnsureCreated(); - } + _databaseChecked = true; + context.Database.EnsureCreated(); } + } - private void AddErrors(IdentityResult result) + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) { - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } + ModelState.AddModelError(string.Empty, error.Description); } + } + + private async Task GetCurrentUserAsync() + { + return await _userManager.GetUserAsync(User); + } - private async Task GetCurrentUserAsync() + private IActionResult RedirectToLocal(string returnUrl) + { + if (Url.IsLocalUrl(returnUrl)) { - return await _userManager.GetUserAsync(User); + return Redirect(returnUrl); } - - private IActionResult RedirectToLocal(string returnUrl) + else { - if (Url.IsLocalUrl(returnUrl)) - { - return Redirect(returnUrl); - } - else - { - return RedirectToAction(nameof(HomeController.Index), "Home"); - } + return RedirectToAction(nameof(HomeController.Index), "Home"); } - - #endregion } + + #endregion } diff --git a/samples/Mvc.Server/Controllers/AuthorizationController.cs b/samples/Mvc.Server/Controllers/AuthorizationController.cs index 90eeb04c..af506b4e 100644 --- a/samples/Mvc.Server/Controllers/AuthorizationController.cs +++ b/samples/Mvc.Server/Controllers/AuthorizationController.cs @@ -23,519 +23,518 @@ using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace Mvc.Server +namespace Mvc.Server; + +public class AuthorizationController : Controller { - public class AuthorizationController : Controller + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictAuthorizationManager _authorizationManager; + private readonly IOpenIddictScopeManager _scopeManager; + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + + public AuthorizationController( + IOpenIddictApplicationManager applicationManager, + IOpenIddictAuthorizationManager authorizationManager, + IOpenIddictScopeManager scopeManager, + SignInManager signInManager, + UserManager userManager) { - private readonly IOpenIddictApplicationManager _applicationManager; - private readonly IOpenIddictAuthorizationManager _authorizationManager; - private readonly IOpenIddictScopeManager _scopeManager; - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - - public AuthorizationController( - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager, - IOpenIddictScopeManager scopeManager, - SignInManager signInManager, - UserManager userManager) - { - _applicationManager = applicationManager; - _authorizationManager = authorizationManager; - _scopeManager = scopeManager; - _signInManager = signInManager; - _userManager = userManager; - } + _applicationManager = applicationManager; + _authorizationManager = authorizationManager; + _scopeManager = scopeManager; + _signInManager = signInManager; + _userManager = userManager; + } - #region Authorization code, implicit and hybrid flows - // Note: to support interactive flows like the code flow, - // you must provide your own authorization endpoint action: + #region Authorization code, implicit and hybrid flows + // Note: to support interactive flows like the code flow, + // you must provide your own authorization endpoint action: - [HttpGet("~/connect/authorize")] - [HttpPost("~/connect/authorize")] - [IgnoreAntiforgeryToken] - public async Task Authorize() + [HttpGet("~/connect/authorize")] + [HttpPost("~/connect/authorize")] + [IgnoreAntiforgeryToken] + public async Task Authorize() + { + var request = HttpContext.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + + // Retrieve the user principal stored in the authentication cookie. + // If a max_age parameter was provided, ensure that the cookie is not too old. + // If the user principal can't be extracted or the cookie is too old, redirect the user to the login page. + var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); + if (result == null || !result.Succeeded || (request.MaxAge != null && result.Properties?.IssuedUtc != null && + DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - - // Retrieve the user principal stored in the authentication cookie. - // If a max_age parameter was provided, ensure that the cookie is not too old. - // If the user principal can't be extracted or the cookie is too old, redirect the user to the login page. - var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); - if (result == null || !result.Succeeded || (request.MaxAge != null && result.Properties?.IssuedUtc != null && - DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) + // If the client application requested promptless authentication, + // return an error indicating that the user is not logged in. + if (request.HasPrompt(Prompts.None)) { - // If the client application requested promptless authentication, - // return an error indicating that the user is not logged in. - if (request.HasPrompt(Prompts.None)) - { - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in." - })); - } - - return Challenge( - authenticationSchemes: IdentityConstants.ApplicationScheme, - properties: new AuthenticationProperties + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary { - RedirectUri = Request.PathBase + Request.Path + QueryString.Create( - Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList()) - }); + [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in." + })); } - // If prompt=login was specified by the client application, - // immediately return the user agent to the login page. - if (request.HasPrompt(Prompts.Login)) - { - // To avoid endless login -> authorization redirects, the prompt=login flag - // is removed from the authorization request payload before redirecting the user. - var prompt = string.Join(" ", request.GetPrompts().Remove(Prompts.Login)); + return Challenge( + authenticationSchemes: IdentityConstants.ApplicationScheme, + properties: new AuthenticationProperties + { + RedirectUri = Request.PathBase + Request.Path + QueryString.Create( + Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList()) + }); + } - var parameters = Request.HasFormContentType ? - Request.Form.Where(parameter => parameter.Key != Parameters.Prompt).ToList() : - Request.Query.Where(parameter => parameter.Key != Parameters.Prompt).ToList(); + // If prompt=login was specified by the client application, + // immediately return the user agent to the login page. + if (request.HasPrompt(Prompts.Login)) + { + // To avoid endless login -> authorization redirects, the prompt=login flag + // is removed from the authorization request payload before redirecting the user. + var prompt = string.Join(" ", request.GetPrompts().Remove(Prompts.Login)); - parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt))); + var parameters = Request.HasFormContentType ? + Request.Form.Where(parameter => parameter.Key != Parameters.Prompt).ToList() : + Request.Query.Where(parameter => parameter.Key != Parameters.Prompt).ToList(); - return Challenge( - authenticationSchemes: IdentityConstants.ApplicationScheme, - properties: new AuthenticationProperties - { - RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) - }); - } + parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt))); + + return Challenge( + authenticationSchemes: IdentityConstants.ApplicationScheme, + properties: new AuthenticationProperties + { + RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) + }); + } - // Retrieve the profile of the logged in user. - var user = await _userManager.GetUserAsync(result.Principal) ?? - throw new InvalidOperationException("The user details cannot be retrieved."); + // Retrieve the profile of the logged in user. + var user = await _userManager.GetUserAsync(result.Principal) ?? + throw new InvalidOperationException("The user details cannot be retrieved."); - // Retrieve the application details from the database. - var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? - throw new InvalidOperationException("Details concerning the calling client application cannot be found."); + // Retrieve the application details from the database. + var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? + throw new InvalidOperationException("Details concerning the calling client application cannot be found."); - // Retrieve the permanent authorizations associated with the user and the calling client application. - var authorizations = await _authorizationManager.FindAsync( - subject: await _userManager.GetUserIdAsync(user), - client : await _applicationManager.GetIdAsync(application), - status : Statuses.Valid, - type : AuthorizationTypes.Permanent, - scopes : request.GetScopes()).ToListAsync(); + // Retrieve the permanent authorizations associated with the user and the calling client application. + var authorizations = await _authorizationManager.FindAsync( + subject: await _userManager.GetUserIdAsync(user), + client : await _applicationManager.GetIdAsync(application), + status : Statuses.Valid, + type : AuthorizationTypes.Permanent, + scopes : request.GetScopes()).ToListAsync(); - switch (await _applicationManager.GetConsentTypeAsync(application)) - { - // If the consent is external (e.g when authorizations are granted by a sysadmin), - // immediately return an error if no authorization can be found in the database. - case ConsentTypes.External when !authorizations.Any(): - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = - "The logged in user is not allowed to access this client application." - })); - - // If the consent is implicit or if an authorization was found, - // return an authorization response without displaying the consent form. - case ConsentTypes.Implicit: - case ConsentTypes.External when authorizations.Any(): - case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent): - var principal = await _signInManager.CreateUserPrincipalAsync(user); - - // Note: in this sample, the granted scopes match the requested scope - // but you may want to allow the user to uncheck specific scopes. - // For that, simply restrict the list of scopes before calling SetScopes. - principal.SetScopes(request.GetScopes()); - principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - - // Automatically create a permanent authorization to avoid requiring explicit consent - // for future authorization or token requests containing the same scopes. - var authorization = authorizations.LastOrDefault(); - if (authorization is null) + switch (await _applicationManager.GetConsentTypeAsync(application)) + { + // If the consent is external (e.g when authorizations are granted by a sysadmin), + // immediately return an error if no authorization can be found in the database. + case ConsentTypes.External when !authorizations.Any(): + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary { - authorization = await _authorizationManager.CreateAsync( - principal: principal, - subject : await _userManager.GetUserIdAsync(user), - client : await _applicationManager.GetIdAsync(application), - type : AuthorizationTypes.Permanent, - scopes : principal.GetScopes()); - } + [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = + "The logged in user is not allowed to access this client application." + })); - principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); + // If the consent is implicit or if an authorization was found, + // return an authorization response without displaying the consent form. + case ConsentTypes.Implicit: + case ConsentTypes.External when authorizations.Any(): + case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent): + var principal = await _signInManager.CreateUserPrincipalAsync(user); - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } - - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - - // At this point, no authorization was found in the database and an error must be returned - // if the client application specified prompt=none in the authorization request. - case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): - case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = - "Interactive user consent is required." - })); - - // In every other case, render the consent form. - default: return View(new AuthorizeViewModel + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + principal.SetScopes(request.GetScopes()); + principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); + + // Automatically create a permanent authorization to avoid requiring explicit consent + // for future authorization or token requests containing the same scopes. + var authorization = authorizations.LastOrDefault(); + if (authorization is null) { - ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application), - Scope = request.Scope - }); - } - } + authorization = await _authorizationManager.CreateAsync( + principal: principal, + subject : await _userManager.GetUserIdAsync(user), + client : await _applicationManager.GetIdAsync(application), + type : AuthorizationTypes.Permanent, + scopes : principal.GetScopes()); + } - [Authorize, FormValueRequired("submit.Accept")] - [HttpPost("~/connect/authorize"), ValidateAntiForgeryToken] - public async Task Accept() - { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); - // Retrieve the profile of the logged in user. - var user = await _userManager.GetUserAsync(User) ?? - throw new InvalidOperationException("The user details cannot be retrieved."); + foreach (var claim in principal.Claims) + { + claim.SetDestinations(GetDestinations(claim, principal)); + } - // Retrieve the application details from the database. - var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? - throw new InvalidOperationException("Details concerning the calling client application cannot be found."); + return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - // Retrieve the permanent authorizations associated with the user and the calling client application. - var authorizations = await _authorizationManager.FindAsync( - subject: await _userManager.GetUserIdAsync(user), - client : await _applicationManager.GetIdAsync(application), - status : Statuses.Valid, - type : AuthorizationTypes.Permanent, - scopes : request.GetScopes()).ToListAsync(); - - // Note: the same check is already made in the other action but is repeated - // here to ensure a malicious user can't abuse this POST-only endpoint and - // force it to return a valid response without the external authorization. - if (!authorizations.Any() && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External)) - { + // At this point, no authorization was found in the database and an error must be returned + // if the client application specified prompt=none in the authorization request. + case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): + case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): return Forbid( authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties: new AuthenticationProperties(new Dictionary { [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = - "The logged in user is not allowed to access this client application." + "Interactive user consent is required." })); - } - - var principal = await _signInManager.CreateUserPrincipalAsync(user); - - // Note: in this sample, the granted scopes match the requested scope - // but you may want to allow the user to uncheck specific scopes. - // For that, simply restrict the list of scopes before calling SetScopes. - principal.SetScopes(request.GetScopes()); - principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - // Automatically create a permanent authorization to avoid requiring explicit consent - // for future authorization or token requests containing the same scopes. - var authorization = authorizations.LastOrDefault(); - if (authorization is null) + // In every other case, render the consent form. + default: return View(new AuthorizeViewModel { - authorization = await _authorizationManager.CreateAsync( - principal: principal, - subject : await _userManager.GetUserIdAsync(user), - client : await _applicationManager.GetIdAsync(application), - type : AuthorizationTypes.Permanent, - scopes : principal.GetScopes()); - } + ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application), + Scope = request.Scope + }); + } + } + + [Authorize, FormValueRequired("submit.Accept")] + [HttpPost("~/connect/authorize"), ValidateAntiForgeryToken] + public async Task Accept() + { + var request = HttpContext.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + + // Retrieve the profile of the logged in user. + var user = await _userManager.GetUserAsync(User) ?? + throw new InvalidOperationException("The user details cannot be retrieved."); + + // Retrieve the application details from the database. + var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? + throw new InvalidOperationException("Details concerning the calling client application cannot be found."); + + // Retrieve the permanent authorizations associated with the user and the calling client application. + var authorizations = await _authorizationManager.FindAsync( + subject: await _userManager.GetUserIdAsync(user), + client : await _applicationManager.GetIdAsync(application), + status : Statuses.Valid, + type : AuthorizationTypes.Permanent, + scopes : request.GetScopes()).ToListAsync(); + + // Note: the same check is already made in the other action but is repeated + // here to ensure a malicious user can't abuse this POST-only endpoint and + // force it to return a valid response without the external authorization. + if (!authorizations.Any() && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External)) + { + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = + "The logged in user is not allowed to access this client application." + })); + } - principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); + var principal = await _signInManager.CreateUserPrincipalAsync(user); - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + principal.SetScopes(request.GetScopes()); + principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + // Automatically create a permanent authorization to avoid requiring explicit consent + // for future authorization or token requests containing the same scopes. + var authorization = authorizations.LastOrDefault(); + if (authorization is null) + { + authorization = await _authorizationManager.CreateAsync( + principal: principal, + subject : await _userManager.GetUserIdAsync(user), + client : await _applicationManager.GetIdAsync(application), + type : AuthorizationTypes.Permanent, + scopes : principal.GetScopes()); } - [Authorize, FormValueRequired("submit.Deny")] - [HttpPost("~/connect/authorize"), ValidateAntiForgeryToken] - // Notify OpenIddict that the authorization grant has been denied by the resource owner - // to redirect the user agent to the client application using the appropriate response_mode. - public IActionResult Deny() => Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - #endregion - - #region Device flow - // Note: to support the device flow, you must provide your own verification endpoint action: - [Authorize, HttpGet("~/connect/verify")] - public async Task Verify() + principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); + + foreach (var claim in principal.Claims) { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + claim.SetDestinations(GetDestinations(claim, principal)); + } - // If the user code was not specified in the query string (e.g as part of the verification_uri_complete), - // render a form to ask the user to enter the user code manually (non-digit chars are automatically ignored). - if (string.IsNullOrEmpty(request.UserCode)) - { - return View(new VerifyViewModel()); - } + // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. + return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } - // Retrieve the claims principal associated with the user code. - var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - if (result.Succeeded) - { - // Retrieve the application details from the database using the client_id stored in the principal. - var application = await _applicationManager.FindByClientIdAsync(result.Principal.GetClaim(Claims.ClientId)) ?? - throw new InvalidOperationException("Details concerning the calling client application cannot be found."); + [Authorize, FormValueRequired("submit.Deny")] + [HttpPost("~/connect/authorize"), ValidateAntiForgeryToken] + // Notify OpenIddict that the authorization grant has been denied by the resource owner + // to redirect the user agent to the client application using the appropriate response_mode. + public IActionResult Deny() => Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + #endregion + + #region Device flow + // Note: to support the device flow, you must provide your own verification endpoint action: + [Authorize, HttpGet("~/connect/verify")] + public async Task Verify() + { + var request = HttpContext.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - // Render a form asking the user to confirm the authorization demand. - return View(new VerifyViewModel - { - ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application), - Scope = string.Join(" ", result.Principal.GetScopes()), - UserCode = request.UserCode - }); - } + // If the user code was not specified in the query string (e.g as part of the verification_uri_complete), + // render a form to ask the user to enter the user code manually (non-digit chars are automatically ignored). + if (string.IsNullOrEmpty(request.UserCode)) + { + return View(new VerifyViewModel()); + } - // Redisplay the form when the user code is not valid. + // Retrieve the claims principal associated with the user code. + var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + if (result.Succeeded) + { + // Retrieve the application details from the database using the client_id stored in the principal. + var application = await _applicationManager.FindByClientIdAsync(result.Principal.GetClaim(Claims.ClientId)) ?? + throw new InvalidOperationException("Details concerning the calling client application cannot be found."); + + // Render a form asking the user to confirm the authorization demand. return View(new VerifyViewModel { - Error = Errors.InvalidToken, - ErrorDescription = "The specified user code is not valid. Please make sure you typed it correctly." + ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application), + Scope = string.Join(" ", result.Principal.GetScopes()), + UserCode = request.UserCode }); } - [Authorize, FormValueRequired("submit.Accept")] - [HttpPost("~/connect/verify"), ValidateAntiForgeryToken] - public async Task VerifyAccept() + // Redisplay the form when the user code is not valid. + return View(new VerifyViewModel { - // Retrieve the profile of the logged in user. - var user = await _userManager.GetUserAsync(User) ?? - throw new InvalidOperationException("The user details cannot be retrieved."); - - // Retrieve the claims principal associated with the user code. - var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - if (result.Succeeded) - { - var principal = await _signInManager.CreateUserPrincipalAsync(user); - - // Note: in this sample, the granted scopes match the requested scope - // but you may want to allow the user to uncheck specific scopes. - // For that, simply restrict the list of scopes before calling SetScopes. - principal.SetScopes(result.Principal.GetScopes()); - principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); + Error = Errors.InvalidToken, + ErrorDescription = "The specified user code is not valid. Please make sure you typed it correctly." + }); + } - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + [Authorize, FormValueRequired("submit.Accept")] + [HttpPost("~/connect/verify"), ValidateAntiForgeryToken] + public async Task VerifyAccept() + { + // Retrieve the profile of the logged in user. + var user = await _userManager.GetUserAsync(User) ?? + throw new InvalidOperationException("The user details cannot be retrieved."); - var properties = new AuthenticationProperties - { - // This property points to the address OpenIddict will automatically - // redirect the user to after validating the authorization demand. - RedirectUri = "/" - }; + // Retrieve the claims principal associated with the user code. + var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + if (result.Succeeded) + { + var principal = await _signInManager.CreateUserPrincipalAsync(user); - return SignIn(principal, properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - } + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + principal.SetScopes(result.Principal.GetScopes()); + principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - // Redisplay the form when the user code is not valid. - return View(new VerifyViewModel + foreach (var claim in principal.Claims) { - Error = Errors.InvalidToken, - ErrorDescription = "The specified user code is not valid. Please make sure you typed it correctly." - }); - } + claim.SetDestinations(GetDestinations(claim, principal)); + } - [Authorize, FormValueRequired("submit.Deny")] - [HttpPost("~/connect/verify"), ValidateAntiForgeryToken] - // Notify OpenIddict that the authorization grant has been denied by the resource owner. - public IActionResult VerifyDeny() => Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties() + var properties = new AuthenticationProperties { // This property points to the address OpenIddict will automatically - // redirect the user to after rejecting the authorization demand. + // redirect the user to after validating the authorization demand. RedirectUri = "/" - }); - #endregion - - #region Logout support for interactive flows like code and implicit - // Note: the logout action is only useful when implementing interactive - // flows like the authorization code flow or the implicit flow. - - [HttpGet("~/connect/logout")] - public IActionResult Logout() => View(); + }; - [ActionName(nameof(Logout)), HttpPost("~/connect/logout"), ValidateAntiForgeryToken] - public async Task LogoutPost() - { - // Ask ASP.NET Core Identity to delete the local and external cookies created - // when the user agent is redirected from the external identity provider - // after a successful authentication flow (e.g Google or Facebook). - await _signInManager.SignOutAsync(); - - // Returning a SignOutResult will ask OpenIddict to redirect the user agent - // to the post_logout_redirect_uri specified by the client application or to - // the RedirectUri specified in the authentication properties if none was set. - return SignOut( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties - { - RedirectUri = "/" - }); + return SignIn(principal, properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } - #endregion - #region Password, authorization code, device and refresh token flows - // Note: to support non-interactive flows like password, - // you must provide your own token endpoint action: + // Redisplay the form when the user code is not valid. + return View(new VerifyViewModel + { + Error = Errors.InvalidToken, + ErrorDescription = "The specified user code is not valid. Please make sure you typed it correctly." + }); + } - [HttpPost("~/connect/token"), Produces("application/json")] - public async Task Exchange() + [Authorize, FormValueRequired("submit.Deny")] + [HttpPost("~/connect/verify"), ValidateAntiForgeryToken] + // Notify OpenIddict that the authorization grant has been denied by the resource owner. + public IActionResult VerifyDeny() => Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties() { - var request = HttpContext.GetOpenIddictServerRequest() ?? - throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); + // This property points to the address OpenIddict will automatically + // redirect the user to after rejecting the authorization demand. + RedirectUri = "/" + }); + #endregion - if (request.IsPasswordGrantType()) - { - var user = await _userManager.FindByNameAsync(request.Username); - if (user is null) - { - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid." - })); - } + #region Logout support for interactive flows like code and implicit + // Note: the logout action is only useful when implementing interactive + // flows like the authorization code flow or the implicit flow. - // Validate the username/password parameters and ensure the account is not locked out. - var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true); - if (!result.Succeeded) - { - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid." - })); - } + [HttpGet("~/connect/logout")] + public IActionResult Logout() => View(); - var principal = await _signInManager.CreateUserPrincipalAsync(user); + [ActionName(nameof(Logout)), HttpPost("~/connect/logout"), ValidateAntiForgeryToken] + public async Task LogoutPost() + { + // Ask ASP.NET Core Identity to delete the local and external cookies created + // when the user agent is redirected from the external identity provider + // after a successful authentication flow (e.g Google or Facebook). + await _signInManager.SignOutAsync(); + + // Returning a SignOutResult will ask OpenIddict to redirect the user agent + // to the post_logout_redirect_uri specified by the client application or to + // the RedirectUri specified in the authentication properties if none was set. + return SignOut( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties + { + RedirectUri = "/" + }); + } + #endregion - // Note: in this sample, the granted scopes match the requested scope - // but you may want to allow the user to uncheck specific scopes. - // For that, simply restrict the list of scopes before calling SetScopes. - principal.SetScopes(request.GetScopes()); - principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); + #region Password, authorization code, device and refresh token flows + // Note: to support non-interactive flows like password, + // you must provide your own token endpoint action: - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + [HttpPost("~/connect/token"), Produces("application/json")] + public async Task Exchange() + { + var request = HttpContext.GetOpenIddictServerRequest() ?? + throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); - // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + if (request.IsPasswordGrantType()) + { + var user = await _userManager.FindByNameAsync(request.Username); + if (user is null) + { + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid." + })); } - else if (request.IsAuthorizationCodeGrantType() || request.IsDeviceCodeGrantType() || request.IsRefreshTokenGrantType()) + // Validate the username/password parameters and ensure the account is not locked out. + var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true); + if (!result.Succeeded) { - // Retrieve the claims principal stored in the authorization code/device code/refresh token. - var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; - - // Retrieve the user profile corresponding to the authorization code/refresh token. - // Note: if you want to automatically invalidate the authorization code/refresh token - // when the user password/roles change, use the following line instead: - // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); - var user = await _userManager.GetUserAsync(principal); - if (user is null) - { - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid." - })); - } + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The username/password couple is invalid." + })); + } - // Ensure the user is still allowed to sign in. - if (!await _signInManager.CanSignInAsync(user)) - { - return Forbid( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in." - })); - } + var principal = await _signInManager.CreateUserPrincipalAsync(user); - foreach (var claim in principal.Claims) - { - claim.SetDestinations(GetDestinations(claim, principal)); - } + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + principal.SetScopes(request.GetScopes()); + principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync()); - // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. - return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + foreach (var claim in principal.Claims) + { + claim.SetDestinations(GetDestinations(claim, principal)); } - throw new InvalidOperationException("The specified grant type is not supported."); + // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. + return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } - #endregion - private IEnumerable GetDestinations(Claim claim, ClaimsPrincipal principal) + else if (request.IsAuthorizationCodeGrantType() || request.IsDeviceCodeGrantType() || request.IsRefreshTokenGrantType()) { - // Note: by default, claims are NOT automatically included in the access and identity tokens. - // To allow OpenIddict to serialize them, you must attach them a destination, that specifies - // whether they should be included in access tokens, in identity tokens or in both. + // Retrieve the claims principal stored in the authorization code/device code/refresh token. + var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; + + // Retrieve the user profile corresponding to the authorization code/refresh token. + // Note: if you want to automatically invalidate the authorization code/refresh token + // when the user password/roles change, use the following line instead: + // var user = _signInManager.ValidateSecurityStampAsync(info.Principal); + var user = await _userManager.GetUserAsync(principal); + if (user is null) + { + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid." + })); + } - switch (claim.Type) + // Ensure the user is still allowed to sign in. + if (!await _signInManager.CanSignInAsync(user)) { - case Claims.Name: - yield return Destinations.AccessToken; + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in." + })); + } - if (principal.HasScope(Scopes.Profile)) - yield return Destinations.IdentityToken; + foreach (var claim in principal.Claims) + { + claim.SetDestinations(GetDestinations(claim, principal)); + } - yield break; + // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. + return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } - case Claims.Email: - yield return Destinations.AccessToken; + throw new InvalidOperationException("The specified grant type is not supported."); + } + #endregion - if (principal.HasScope(Scopes.Email)) - yield return Destinations.IdentityToken; + private IEnumerable GetDestinations(Claim claim, ClaimsPrincipal principal) + { + // Note: by default, claims are NOT automatically included in the access and identity tokens. + // To allow OpenIddict to serialize them, you must attach them a destination, that specifies + // whether they should be included in access tokens, in identity tokens or in both. - yield break; + switch (claim.Type) + { + case Claims.Name: + yield return Destinations.AccessToken; - case Claims.Role: - yield return Destinations.AccessToken; + if (principal.HasScope(Scopes.Profile)) + yield return Destinations.IdentityToken; - if (principal.HasScope(Scopes.Roles)) - yield return Destinations.IdentityToken; + yield break; - yield break; + case Claims.Email: + yield return Destinations.AccessToken; - // Never include the security stamp in the access and identity tokens, as it's a secret value. - case "AspNet.Identity.SecurityStamp": yield break; + if (principal.HasScope(Scopes.Email)) + yield return Destinations.IdentityToken; - default: - yield return Destinations.AccessToken; - yield break; - } + yield break; + + case Claims.Role: + yield return Destinations.AccessToken; + + if (principal.HasScope(Scopes.Roles)) + yield return Destinations.IdentityToken; + + yield break; + + // Never include the security stamp in the access and identity tokens, as it's a secret value. + case "AspNet.Identity.SecurityStamp": yield break; + + default: + yield return Destinations.AccessToken; + yield break; } } -} \ No newline at end of file +} diff --git a/samples/Mvc.Server/Controllers/ErrorController.cs b/samples/Mvc.Server/Controllers/ErrorController.cs index fd5ca6e3..116953ee 100644 --- a/samples/Mvc.Server/Controllers/ErrorController.cs +++ b/samples/Mvc.Server/Controllers/ErrorController.cs @@ -8,26 +8,25 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Mvc; using Mvc.Server.ViewModels.Shared; -namespace Mvc.Server +namespace Mvc.Server; + +public class ErrorController : Controller { - public class ErrorController : Controller + [HttpGet, HttpPost, Route("~/error")] + public IActionResult Error() { - [HttpGet, HttpPost, Route("~/error")] - public IActionResult Error() + // If the error was not caused by an invalid + // OIDC request, display a generic error page. + var response = HttpContext.GetOpenIddictServerResponse(); + if (response is null) { - // If the error was not caused by an invalid - // OIDC request, display a generic error page. - var response = HttpContext.GetOpenIddictServerResponse(); - if (response is null) - { - return View(new ErrorViewModel()); - } - - return View(new ErrorViewModel - { - Error = response.Error, - ErrorDescription = response.ErrorDescription - }); + return View(new ErrorViewModel()); } + + return View(new ErrorViewModel + { + Error = response.Error, + ErrorDescription = response.ErrorDescription + }); } -} \ No newline at end of file +} diff --git a/samples/Mvc.Server/Controllers/HomeController.cs b/samples/Mvc.Server/Controllers/HomeController.cs index f8a8902d..1bdf3f56 100644 --- a/samples/Mvc.Server/Controllers/HomeController.cs +++ b/samples/Mvc.Server/Controllers/HomeController.cs @@ -1,31 +1,30 @@ using Microsoft.AspNetCore.Mvc; -namespace Mvc.Server.Controllers +namespace Mvc.Server.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller + public IActionResult Index() { - public IActionResult Index() - { - return View(); - } + return View(); + } - public IActionResult About() - { - ViewData["Message"] = "Your application description page."; + public IActionResult About() + { + ViewData["Message"] = "Your application description page."; - return View(); - } + return View(); + } - public IActionResult Contact() - { - ViewData["Message"] = "Your contact page."; + public IActionResult Contact() + { + ViewData["Message"] = "Your contact page."; - return View(); - } + return View(); + } - public IActionResult Error() - { - return View("~/Views/Shared/Error.cshtml"); - } + public IActionResult Error() + { + return View("~/Views/Shared/Error.cshtml"); } } diff --git a/samples/Mvc.Server/Controllers/ManageController.cs b/samples/Mvc.Server/Controllers/ManageController.cs index ffb34014..90a17e77 100644 --- a/samples/Mvc.Server/Controllers/ManageController.cs +++ b/samples/Mvc.Server/Controllers/ManageController.cs @@ -8,338 +8,337 @@ using Mvc.Server.Models; using Mvc.Server.Services; using Mvc.Server.ViewModels.Manage; -namespace Mvc.Server.Controllers +namespace Mvc.Server.Controllers; + +[Authorize] +public class ManageController : Controller { - [Authorize] - public class ManageController : Controller + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; + private readonly ISmsSender _smsSender; + private readonly ILogger _logger; + + public ManageController( + UserManager userManager, + SignInManager signInManager, + IEmailSender emailSender, + ISmsSender smsSender, + ILoggerFactory loggerFactory) { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly IEmailSender _emailSender; - private readonly ISmsSender _smsSender; - private readonly ILogger _logger; + _userManager = userManager; + _signInManager = signInManager; + _emailSender = emailSender; + _smsSender = smsSender; + _logger = loggerFactory.CreateLogger(); + } - public ManageController( - UserManager userManager, - SignInManager signInManager, - IEmailSender emailSender, - ISmsSender smsSender, - ILoggerFactory loggerFactory) - { - _userManager = userManager; - _signInManager = signInManager; - _emailSender = emailSender; - _smsSender = smsSender; - _logger = loggerFactory.CreateLogger(); - } + // + // GET: /Manage/Index + [HttpGet] + public async Task Index(ManageMessageId? message = null) + { + ViewData["StatusMessage"] = + message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed." + : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set." + : message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set." + : message == ManageMessageId.Error ? "An error has occurred." + : message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added." + : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed." + : ""; - // - // GET: /Manage/Index - [HttpGet] - public async Task Index(ManageMessageId? message = null) + var user = await GetCurrentUserAsync(); + var model = new IndexViewModel { - ViewData["StatusMessage"] = - message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed." - : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set." - : message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set." - : message == ManageMessageId.Error ? "An error has occurred." - : message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added." - : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed." - : ""; - - var user = await GetCurrentUserAsync(); - var model = new IndexViewModel - { - HasPassword = await _userManager.HasPasswordAsync(user), - PhoneNumber = await _userManager.GetPhoneNumberAsync(user), - TwoFactor = await _userManager.GetTwoFactorEnabledAsync(user), - Logins = await _userManager.GetLoginsAsync(user), - BrowserRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user) - }; - return View(model); - } + HasPassword = await _userManager.HasPasswordAsync(user), + PhoneNumber = await _userManager.GetPhoneNumberAsync(user), + TwoFactor = await _userManager.GetTwoFactorEnabledAsync(user), + Logins = await _userManager.GetLoginsAsync(user), + BrowserRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user) + }; + return View(model); + } - // - // POST: /Manage/RemoveLogin - [HttpPost] - [ValidateAntiForgeryToken] - public async Task RemoveLogin(RemoveLoginViewModel account) + // + // POST: /Manage/RemoveLogin + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RemoveLogin(RemoveLoginViewModel account) + { + ManageMessageId? message = ManageMessageId.Error; + var user = await GetCurrentUserAsync(); + if (user is not null) { - ManageMessageId? message = ManageMessageId.Error; - var user = await GetCurrentUserAsync(); - if (user is not null) + var result = await _userManager.RemoveLoginAsync(user, account.LoginProvider, account.ProviderKey); + if (result.Succeeded) { - var result = await _userManager.RemoveLoginAsync(user, account.LoginProvider, account.ProviderKey); - if (result.Succeeded) - { - await _signInManager.SignInAsync(user, isPersistent: false); - message = ManageMessageId.RemoveLoginSuccess; - } + await _signInManager.SignInAsync(user, isPersistent: false); + message = ManageMessageId.RemoveLoginSuccess; } - return RedirectToAction(nameof(ManageLogins), new { Message = message }); } + return RedirectToAction(nameof(ManageLogins), new { Message = message }); + } - // - // GET: /Manage/AddPhoneNumber - public IActionResult AddPhoneNumber() - { - return View(); - } + // + // GET: /Manage/AddPhoneNumber + public IActionResult AddPhoneNumber() + { + return View(); + } - // - // POST: /Manage/AddPhoneNumber - [HttpPost] - [ValidateAntiForgeryToken] - public async Task AddPhoneNumber(AddPhoneNumberViewModel model) + // + // POST: /Manage/AddPhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task AddPhoneNumber(AddPhoneNumberViewModel model) + { + if (!ModelState.IsValid) { - if (!ModelState.IsValid) - { - return View(model); - } - // Generate the token and send it - var user = await GetCurrentUserAsync(); - var code = await _userManager.GenerateChangePhoneNumberTokenAsync(user, model.PhoneNumber); - await _smsSender.SendSmsAsync(model.PhoneNumber, "Your security code is: " + code); - return RedirectToAction(nameof(VerifyPhoneNumber), new { PhoneNumber = model.PhoneNumber }); + return View(model); } + // Generate the token and send it + var user = await GetCurrentUserAsync(); + var code = await _userManager.GenerateChangePhoneNumberTokenAsync(user, model.PhoneNumber); + await _smsSender.SendSmsAsync(model.PhoneNumber, "Your security code is: " + code); + return RedirectToAction(nameof(VerifyPhoneNumber), new { PhoneNumber = model.PhoneNumber }); + } - // - // POST: /Manage/EnableTwoFactorAuthentication - [HttpPost] - [ValidateAntiForgeryToken] - public async Task EnableTwoFactorAuthentication() + // + // POST: /Manage/EnableTwoFactorAuthentication + [HttpPost] + [ValidateAntiForgeryToken] + public async Task EnableTwoFactorAuthentication() + { + var user = await GetCurrentUserAsync(); + if (user is not null) { - var user = await GetCurrentUserAsync(); - if (user is not null) - { - await _userManager.SetTwoFactorEnabledAsync(user, true); - await _signInManager.SignInAsync(user, isPersistent: false); - _logger.LogInformation(1, "User enabled two-factor authentication."); - } - return RedirectToAction(nameof(Index), "Manage"); + await _userManager.SetTwoFactorEnabledAsync(user, true); + await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation(1, "User enabled two-factor authentication."); } + return RedirectToAction(nameof(Index), "Manage"); + } - // - // POST: /Manage/DisableTwoFactorAuthentication - [HttpPost] - [ValidateAntiForgeryToken] - public async Task DisableTwoFactorAuthentication() + // + // POST: /Manage/DisableTwoFactorAuthentication + [HttpPost] + [ValidateAntiForgeryToken] + public async Task DisableTwoFactorAuthentication() + { + var user = await GetCurrentUserAsync(); + if (user is not null) { - var user = await GetCurrentUserAsync(); - if (user is not null) - { - await _userManager.SetTwoFactorEnabledAsync(user, false); - await _signInManager.SignInAsync(user, isPersistent: false); - _logger.LogInformation(2, "User disabled two-factor authentication."); - } - return RedirectToAction(nameof(Index), "Manage"); + await _userManager.SetTwoFactorEnabledAsync(user, false); + await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation(2, "User disabled two-factor authentication."); } + return RedirectToAction(nameof(Index), "Manage"); + } + + // + // GET: /Manage/VerifyPhoneNumber + [HttpGet] + public async Task VerifyPhoneNumber(string phoneNumber) + { + var code = await _userManager.GenerateChangePhoneNumberTokenAsync(await GetCurrentUserAsync(), phoneNumber); + // Send an SMS to verify the phone number + return phoneNumber is null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber }); + } - // - // GET: /Manage/VerifyPhoneNumber - [HttpGet] - public async Task VerifyPhoneNumber(string phoneNumber) + // + // POST: /Manage/VerifyPhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task VerifyPhoneNumber(VerifyPhoneNumberViewModel model) + { + if (!ModelState.IsValid) { - var code = await _userManager.GenerateChangePhoneNumberTokenAsync(await GetCurrentUserAsync(), phoneNumber); - // Send an SMS to verify the phone number - return phoneNumber is null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber }); + return View(model); } - - // - // POST: /Manage/VerifyPhoneNumber - [HttpPost] - [ValidateAntiForgeryToken] - public async Task VerifyPhoneNumber(VerifyPhoneNumberViewModel model) + var user = await GetCurrentUserAsync(); + if (user is not null) { - if (!ModelState.IsValid) + var result = await _userManager.ChangePhoneNumberAsync(user, model.PhoneNumber, model.Code); + if (result.Succeeded) { - return View(model); - } - var user = await GetCurrentUserAsync(); - if (user is not null) - { - var result = await _userManager.ChangePhoneNumberAsync(user, model.PhoneNumber, model.Code); - if (result.Succeeded) - { - await _signInManager.SignInAsync(user, isPersistent: false); - return RedirectToAction(nameof(Index), new { Message = ManageMessageId.AddPhoneSuccess }); - } + await _signInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.AddPhoneSuccess }); } - // If we got this far, something failed, redisplay the form - ModelState.AddModelError(string.Empty, "Failed to verify phone number"); - return View(model); } + // If we got this far, something failed, redisplay the form + ModelState.AddModelError(string.Empty, "Failed to verify phone number"); + return View(model); + } - // - // GET: /Manage/RemovePhoneNumber - [HttpPost] - [ValidateAntiForgeryToken] - public async Task RemovePhoneNumber() + // + // GET: /Manage/RemovePhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RemovePhoneNumber() + { + var user = await GetCurrentUserAsync(); + if (user is not null) { - var user = await GetCurrentUserAsync(); - if (user is not null) + var result = await _userManager.SetPhoneNumberAsync(user, null); + if (result.Succeeded) { - var result = await _userManager.SetPhoneNumberAsync(user, null); - if (result.Succeeded) - { - await _signInManager.SignInAsync(user, isPersistent: false); - return RedirectToAction(nameof(Index), new { Message = ManageMessageId.RemovePhoneSuccess }); - } + await _signInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.RemovePhoneSuccess }); } - return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); } + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); + } + + // + // GET: /Manage/ChangePassword + [HttpGet] + public IActionResult ChangePassword() + { + return View(); + } - // - // GET: /Manage/ChangePassword - [HttpGet] - public IActionResult ChangePassword() + // + // POST: /Manage/ChangePassword + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ChangePassword(ChangePasswordViewModel model) + { + if (!ModelState.IsValid) { - return View(); + return View(model); } - - // - // POST: /Manage/ChangePassword - [HttpPost] - [ValidateAntiForgeryToken] - public async Task ChangePassword(ChangePasswordViewModel model) + var user = await GetCurrentUserAsync(); + if (user is not null) { - if (!ModelState.IsValid) - { - return View(model); - } - var user = await GetCurrentUserAsync(); - if (user is not null) + var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); + if (result.Succeeded) { - var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); - if (result.Succeeded) - { - await _signInManager.SignInAsync(user, isPersistent: false); - _logger.LogInformation(3, "User changed their password successfully."); - return RedirectToAction(nameof(Index), new { Message = ManageMessageId.ChangePasswordSuccess }); - } - AddErrors(result); - return View(model); + await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation(3, "User changed their password successfully."); + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.ChangePasswordSuccess }); } - return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); + AddErrors(result); + return View(model); } + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); + } - // - // GET: /Manage/SetPassword - [HttpGet] - public IActionResult SetPassword() - { - return View(); - } + // + // GET: /Manage/SetPassword + [HttpGet] + public IActionResult SetPassword() + { + return View(); + } - // - // POST: /Manage/SetPassword - [HttpPost] - [ValidateAntiForgeryToken] - public async Task SetPassword(SetPasswordViewModel model) + // + // POST: /Manage/SetPassword + [HttpPost] + [ValidateAntiForgeryToken] + public async Task SetPassword(SetPasswordViewModel model) + { + if (!ModelState.IsValid) { - if (!ModelState.IsValid) - { - return View(model); - } - - var user = await GetCurrentUserAsync(); - if (user is not null) - { - var result = await _userManager.AddPasswordAsync(user, model.NewPassword); - if (result.Succeeded) - { - await _signInManager.SignInAsync(user, isPersistent: false); - return RedirectToAction(nameof(Index), new { Message = ManageMessageId.SetPasswordSuccess }); - } - AddErrors(result); - return View(model); - } - return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); + return View(model); } - //GET: /Manage/ManageLogins - [HttpGet] - public async Task ManageLogins(ManageMessageId? message = null) + var user = await GetCurrentUserAsync(); + if (user is not null) { - ViewData["StatusMessage"] = - message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." - : message == ManageMessageId.AddLoginSuccess ? "The external login was added." - : message == ManageMessageId.Error ? "An error has occurred." - : ""; - var user = await GetCurrentUserAsync(); - if (user is null) + var result = await _userManager.AddPasswordAsync(user, model.NewPassword); + if (result.Succeeded) { - return View("Error"); + await _signInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.SetPasswordSuccess }); } - var userLogins = await _userManager.GetLoginsAsync(user); - var otherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).Where(auth => userLogins.All(ul => auth.Name != ul.LoginProvider)).ToList(); - ViewData["ShowRemoveButton"] = user.PasswordHash is not null || userLogins.Count > 1; - return View(new ManageLoginsViewModel - { - CurrentLogins = userLogins, - OtherLogins = otherLogins - }); + AddErrors(result); + return View(model); } + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); + } - // - // POST: /Manage/LinkLogin - [HttpPost] - [ValidateAntiForgeryToken] - public IActionResult LinkLogin(string provider) + //GET: /Manage/ManageLogins + [HttpGet] + public async Task ManageLogins(ManageMessageId? message = null) + { + ViewData["StatusMessage"] = + message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." + : message == ManageMessageId.AddLoginSuccess ? "The external login was added." + : message == ManageMessageId.Error ? "An error has occurred." + : ""; + var user = await GetCurrentUserAsync(); + if (user is null) { - // Request a redirect to the external login provider to link a login for the current user - var redirectUrl = Url.Action("LinkLoginCallback", "Manage"); - var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User)); - return Challenge(properties, provider); + return View("Error"); } - - // - // GET: /Manage/LinkLoginCallback - [HttpGet] - public async Task LinkLoginCallback() + var userLogins = await _userManager.GetLoginsAsync(user); + var otherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).Where(auth => userLogins.All(ul => auth.Name != ul.LoginProvider)).ToList(); + ViewData["ShowRemoveButton"] = user.PasswordHash is not null || userLogins.Count > 1; + return View(new ManageLoginsViewModel { - var user = await GetCurrentUserAsync(); - if (user is null) - { - return View("Error"); - } - var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user)); - if (info is null) - { - return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error }); - } - var result = await _userManager.AddLoginAsync(user, info); - var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error; - return RedirectToAction(nameof(ManageLogins), new { Message = message }); - } + CurrentLogins = userLogins, + OtherLogins = otherLogins + }); + } - #region Helpers + // + // POST: /Manage/LinkLogin + [HttpPost] + [ValidateAntiForgeryToken] + public IActionResult LinkLogin(string provider) + { + // Request a redirect to the external login provider to link a login for the current user + var redirectUrl = Url.Action("LinkLoginCallback", "Manage"); + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User)); + return Challenge(properties, provider); + } - private void AddErrors(IdentityResult result) + // + // GET: /Manage/LinkLoginCallback + [HttpGet] + public async Task LinkLoginCallback() + { + var user = await GetCurrentUserAsync(); + if (user is null) { - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } + return View("Error"); } - - public enum ManageMessageId + var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user)); + if (info is null) { - AddPhoneSuccess, - AddLoginSuccess, - ChangePasswordSuccess, - SetTwoFactorSuccess, - SetPasswordSuccess, - RemoveLoginSuccess, - RemovePhoneSuccess, - Error + return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error }); } + var result = await _userManager.AddLoginAsync(user, info); + var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error; + return RedirectToAction(nameof(ManageLogins), new { Message = message }); + } + + #region Helpers - private Task GetCurrentUserAsync() + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) { - return _userManager.GetUserAsync(User); + ModelState.AddModelError(string.Empty, error.Description); } + } + + public enum ManageMessageId + { + AddPhoneSuccess, + AddLoginSuccess, + ChangePasswordSuccess, + SetTwoFactorSuccess, + SetPasswordSuccess, + RemoveLoginSuccess, + RemovePhoneSuccess, + Error + } - #endregion + private Task GetCurrentUserAsync() + { + return _userManager.GetUserAsync(User); } -} \ No newline at end of file + + #endregion +} diff --git a/samples/Mvc.Server/Controllers/ResourceController.cs b/samples/Mvc.Server/Controllers/ResourceController.cs index 0348032f..6238f924 100644 --- a/samples/Mvc.Server/Controllers/ResourceController.cs +++ b/samples/Mvc.Server/Controllers/ResourceController.cs @@ -9,50 +9,49 @@ using OpenIddict.Abstractions; using OpenIddict.Validation.AspNetCore; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace Mvc.Server.Controllers +namespace Mvc.Server.Controllers; + +[Route("api")] +public class ResourceController : Controller { - [Route("api")] - public class ResourceController : Controller - { - private readonly UserManager _userManager; + private readonly UserManager _userManager; - public ResourceController(UserManager userManager) - => _userManager = userManager; + public ResourceController(UserManager userManager) + => _userManager = userManager; - [Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)] - [HttpGet("message")] - public async Task GetMessage() + [Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)] + [HttpGet("message")] + public async Task GetMessage() + { + // This demo action requires that the client application be granted the "demo_api" scope. + // If it was not granted, a detailed error is returned to the client application to inform it + // that the authorization process must be restarted with the specified scope to access this API. + if (!User.HasScope("demo_api")) { - // This demo action requires that the client application be granted the "demo_api" scope. - // If it was not granted, a detailed error is returned to the client application to inform it - // that the authorization process must be restarted with the specified scope to access this API. - if (!User.HasScope("demo_api")) - { - return Forbid( - authenticationSchemes: OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictValidationAspNetCoreConstants.Properties.Scope] = "demo_api", - [OpenIddictValidationAspNetCoreConstants.Properties.Error] = Errors.InsufficientScope, - [OpenIddictValidationAspNetCoreConstants.Properties.ErrorDescription] = - "The 'demo_api' scope is required to perform this action." - })); - } - - var user = await _userManager.GetUserAsync(User); - if (user is null) - { - return Challenge( - authenticationSchemes: OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictValidationAspNetCoreConstants.Properties.Error] = Errors.InvalidToken, - [OpenIddictValidationAspNetCoreConstants.Properties.ErrorDescription] = - "The specified access token is bound to an account that no longer exists." - })); - } + return Forbid( + authenticationSchemes: OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictValidationAspNetCoreConstants.Properties.Scope] = "demo_api", + [OpenIddictValidationAspNetCoreConstants.Properties.Error] = Errors.InsufficientScope, + [OpenIddictValidationAspNetCoreConstants.Properties.ErrorDescription] = + "The 'demo_api' scope is required to perform this action." + })); + } - return Content($"{user.UserName} has been successfully authenticated."); + var user = await _userManager.GetUserAsync(User); + if (user is null) + { + return Challenge( + authenticationSchemes: OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictValidationAspNetCoreConstants.Properties.Error] = Errors.InvalidToken, + [OpenIddictValidationAspNetCoreConstants.Properties.ErrorDescription] = + "The specified access token is bound to an account that no longer exists." + })); } + + return Content($"{user.UserName} has been successfully authenticated."); } -} \ No newline at end of file +} diff --git a/samples/Mvc.Server/Controllers/UserinfoController.cs b/samples/Mvc.Server/Controllers/UserinfoController.cs index 3aaec6f3..6c7a4fe6 100644 --- a/samples/Mvc.Server/Controllers/UserinfoController.cs +++ b/samples/Mvc.Server/Controllers/UserinfoController.cs @@ -10,60 +10,59 @@ using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace Mvc.Server.Controllers +namespace Mvc.Server.Controllers; + +public class UserinfoController : Controller { - public class UserinfoController : Controller - { - private readonly UserManager _userManager; + private readonly UserManager _userManager; - public UserinfoController(UserManager userManager) - => _userManager = userManager; + public UserinfoController(UserManager userManager) + => _userManager = userManager; - [Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)] - [HttpGet("~/connect/userinfo"), HttpPost("~/connect/userinfo")] - [IgnoreAntiforgeryToken, Produces("application/json")] - public async Task Userinfo() + [Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)] + [HttpGet("~/connect/userinfo"), HttpPost("~/connect/userinfo")] + [IgnoreAntiforgeryToken, Produces("application/json")] + public async Task Userinfo() + { + var user = await _userManager.GetUserAsync(User); + if (user is null) { - var user = await _userManager.GetUserAsync(User); - if (user is null) - { - return Challenge( - authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, - properties: new AuthenticationProperties(new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken, - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = - "The specified access token is bound to an account that no longer exists." - })); - } + return Challenge( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = + "The specified access token is bound to an account that no longer exists." + })); + } - var claims = new Dictionary(StringComparer.Ordinal) - { - // Note: the "sub" claim is a mandatory claim and must be included in the JSON response. - [Claims.Subject] = await _userManager.GetUserIdAsync(user) - }; + var claims = new Dictionary(StringComparer.Ordinal) + { + // Note: the "sub" claim is a mandatory claim and must be included in the JSON response. + [Claims.Subject] = await _userManager.GetUserIdAsync(user) + }; - if (User.HasScope(Scopes.Email)) - { - claims[Claims.Email] = await _userManager.GetEmailAsync(user); - claims[Claims.EmailVerified] = await _userManager.IsEmailConfirmedAsync(user); - } + if (User.HasScope(Scopes.Email)) + { + claims[Claims.Email] = await _userManager.GetEmailAsync(user); + claims[Claims.EmailVerified] = await _userManager.IsEmailConfirmedAsync(user); + } - if (User.HasScope(Scopes.Phone)) - { - claims[Claims.PhoneNumber] = await _userManager.GetPhoneNumberAsync(user); - claims[Claims.PhoneNumberVerified] = await _userManager.IsPhoneNumberConfirmedAsync(user); - } + if (User.HasScope(Scopes.Phone)) + { + claims[Claims.PhoneNumber] = await _userManager.GetPhoneNumberAsync(user); + claims[Claims.PhoneNumberVerified] = await _userManager.IsPhoneNumberConfirmedAsync(user); + } - if (User.HasScope(Scopes.Roles)) - { - claims[Claims.Role] = await _userManager.GetRolesAsync(user); - } + if (User.HasScope(Scopes.Roles)) + { + claims[Claims.Role] = await _userManager.GetRolesAsync(user); + } - // Note: the complete list of standard claims supported by the OpenID Connect specification - // can be found here: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims + // Note: the complete list of standard claims supported by the OpenID Connect specification + // can be found here: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims - return Ok(claims); - } + return Ok(claims); } } diff --git a/samples/Mvc.Server/Helpers/AsyncEnumerableExtensions.cs b/samples/Mvc.Server/Helpers/AsyncEnumerableExtensions.cs index 4a69b1d3..7d61bd67 100644 --- a/samples/Mvc.Server/Helpers/AsyncEnumerableExtensions.cs +++ b/samples/Mvc.Server/Helpers/AsyncEnumerableExtensions.cs @@ -2,30 +2,29 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Mvc.Server.Helpers +namespace Mvc.Server.Helpers; + +public static class AsyncEnumerableExtensions { - public static class AsyncEnumerableExtensions + public static Task> ToListAsync(this IAsyncEnumerable source) { - public static Task> ToListAsync(this IAsyncEnumerable source) + if (source is null) { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } + throw new ArgumentNullException(nameof(source)); + } - return ExecuteAsync(); + return ExecuteAsync(); - async Task> ExecuteAsync() - { - var list = new List(); - - await foreach (var element in source) - { - list.Add(element); - } + async Task> ExecuteAsync() + { + var list = new List(); - return list; + await foreach (var element in source) + { + list.Add(element); } + + return list; } } } diff --git a/samples/Mvc.Server/Helpers/FormValueRequiredAttribute.cs b/samples/Mvc.Server/Helpers/FormValueRequiredAttribute.cs index 56183d1c..30e1a873 100644 --- a/samples/Mvc.Server/Helpers/FormValueRequiredAttribute.cs +++ b/samples/Mvc.Server/Helpers/FormValueRequiredAttribute.cs @@ -3,38 +3,37 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Routing; -namespace Mvc.Server.Helpers +namespace Mvc.Server.Helpers; + +public sealed class FormValueRequiredAttribute : ActionMethodSelectorAttribute { - public sealed class FormValueRequiredAttribute : ActionMethodSelectorAttribute + private readonly string _name; + + public FormValueRequiredAttribute(string name) { - private readonly string _name; + _name = name; + } - public FormValueRequiredAttribute(string name) + public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action) + { + if (string.Equals(routeContext.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase) || + string.Equals(routeContext.HttpContext.Request.Method, "HEAD", StringComparison.OrdinalIgnoreCase) || + string.Equals(routeContext.HttpContext.Request.Method, "DELETE", StringComparison.OrdinalIgnoreCase) || + string.Equals(routeContext.HttpContext.Request.Method, "TRACE", StringComparison.OrdinalIgnoreCase)) { - _name = name; + return false; } - public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action) + if (string.IsNullOrEmpty(routeContext.HttpContext.Request.ContentType)) { - if (string.Equals(routeContext.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase) || - string.Equals(routeContext.HttpContext.Request.Method, "HEAD", StringComparison.OrdinalIgnoreCase) || - string.Equals(routeContext.HttpContext.Request.Method, "DELETE", StringComparison.OrdinalIgnoreCase) || - string.Equals(routeContext.HttpContext.Request.Method, "TRACE", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (string.IsNullOrEmpty(routeContext.HttpContext.Request.ContentType)) - { - return false; - } - - if (!routeContext.HttpContext.Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) - { - return false; - } + return false; + } - return !string.IsNullOrEmpty(routeContext.HttpContext.Request.Form[_name]); + if (!routeContext.HttpContext.Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) + { + return false; } + + return !string.IsNullOrEmpty(routeContext.HttpContext.Request.Form[_name]); } } diff --git a/samples/Mvc.Server/Models/ApplicationDbContext.cs b/samples/Mvc.Server/Models/ApplicationDbContext.cs index 9234da9e..af3e843b 100644 --- a/samples/Mvc.Server/Models/ApplicationDbContext.cs +++ b/samples/Mvc.Server/Models/ApplicationDbContext.cs @@ -1,20 +1,19 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -namespace Mvc.Server.Models +namespace Mvc.Server.Models; + +public class ApplicationDbContext : IdentityDbContext { - public class ApplicationDbContext : IdentityDbContext - { - public ApplicationDbContext(DbContextOptions options) - : base(options) { } + public ApplicationDbContext(DbContextOptions options) + : base(options) { } - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } + // Customize the ASP.NET Identity model and override the defaults if needed. + // For example, you can rename the ASP.NET Identity table names and more. + // Add your customizations after calling base.OnModelCreating(builder); } } diff --git a/samples/Mvc.Server/Models/ApplicationUser.cs b/samples/Mvc.Server/Models/ApplicationUser.cs index 2f3f4a21..6a98a66a 100644 --- a/samples/Mvc.Server/Models/ApplicationUser.cs +++ b/samples/Mvc.Server/Models/ApplicationUser.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Identity; -namespace Mvc.Server.Models -{ - // Add profile data for application users by adding properties to the ApplicationUser class - public class ApplicationUser : IdentityUser { } -} +namespace Mvc.Server.Models; + +// Add profile data for application users by adding properties to the ApplicationUser class +public class ApplicationUser : IdentityUser { } diff --git a/samples/Mvc.Server/Program.cs b/samples/Mvc.Server/Program.cs index 16b0ecfa..325ba036 100644 --- a/samples/Mvc.Server/Program.cs +++ b/samples/Mvc.Server/Program.cs @@ -1,15 +1,14 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -namespace Mvc.Server +namespace Mvc.Server; + +public static class Program { - public static class Program - { - public static void Main(string[] args) => - CreateHostBuilder(args).Build().Run(); + public static void Main(string[] args) => + CreateHostBuilder(args).Build().Run(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(builder => builder.UseStartup()); - } + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(builder => builder.UseStartup()); } diff --git a/samples/Mvc.Server/Services/IEmailSender.cs b/samples/Mvc.Server/Services/IEmailSender.cs index d4c4f1af..eee78a11 100644 --- a/samples/Mvc.Server/Services/IEmailSender.cs +++ b/samples/Mvc.Server/Services/IEmailSender.cs @@ -1,9 +1,8 @@ using System.Threading.Tasks; -namespace Mvc.Server.Services +namespace Mvc.Server.Services; + +public interface IEmailSender { - public interface IEmailSender - { - Task SendEmailAsync(string email, string subject, string message); - } + Task SendEmailAsync(string email, string subject, string message); } diff --git a/samples/Mvc.Server/Services/ISmsSender.cs b/samples/Mvc.Server/Services/ISmsSender.cs index 80e505df..02da67e5 100644 --- a/samples/Mvc.Server/Services/ISmsSender.cs +++ b/samples/Mvc.Server/Services/ISmsSender.cs @@ -1,9 +1,8 @@ using System.Threading.Tasks; -namespace Mvc.Server.Services +namespace Mvc.Server.Services; + +public interface ISmsSender { - public interface ISmsSender - { - Task SendSmsAsync(string number, string message); - } + Task SendSmsAsync(string number, string message); } diff --git a/samples/Mvc.Server/Services/MessageServices.cs b/samples/Mvc.Server/Services/MessageServices.cs index a0794786..58cd9623 100644 --- a/samples/Mvc.Server/Services/MessageServices.cs +++ b/samples/Mvc.Server/Services/MessageServices.cs @@ -1,22 +1,21 @@ using System.Threading.Tasks; -namespace Mvc.Server.Services +namespace Mvc.Server.Services; + +// This class is used by the application to send Email and SMS +// when you turn on two-factor authentication in ASP.NET Identity. +// For more details see this link http://go.microsoft.com/fwlink/?LinkID=532713 +public class AuthMessageSender : IEmailSender, ISmsSender { - // This class is used by the application to send Email and SMS - // when you turn on two-factor authentication in ASP.NET Identity. - // For more details see this link http://go.microsoft.com/fwlink/?LinkID=532713 - public class AuthMessageSender : IEmailSender, ISmsSender + public Task SendEmailAsync(string email, string subject, string message) { - public Task SendEmailAsync(string email, string subject, string message) - { - // Plug in your email service here to send an email. - return Task.FromResult(0); - } + // Plug in your email service here to send an email. + return Task.FromResult(0); + } - public Task SendSmsAsync(string number, string message) - { - // Plug in your SMS service here to send a text message. - return Task.FromResult(0); - } + public Task SendSmsAsync(string number, string message) + { + // Plug in your SMS service here to send a text message. + return Task.FromResult(0); } } diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index 433a1e7b..459d3fd7 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -8,193 +8,192 @@ using Mvc.Server.Services; using Quartz; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace Mvc.Server +namespace Mvc.Server; + +public class Startup { - public class Startup - { - public Startup(IConfiguration configuration) - => Configuration = configuration; + public Startup(IConfiguration configuration) + => Configuration = configuration; - public IConfiguration Configuration { get; } + public IConfiguration Configuration { get; } - public void ConfigureServices(IServiceCollection services) - { - services.AddControllersWithViews(); + public void ConfigureServices(IServiceCollection services) + { + services.AddControllersWithViews(); - services.AddDbContext(options => - { - // Configure the context to use Microsoft SQL Server. - options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); + services.AddDbContext(options => + { + // Configure the context to use Microsoft SQL Server. + options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); + + // Register the entity sets needed by OpenIddict. + // Note: use the generic overload if you need + // to replace the default OpenIddict entities. + options.UseOpenIddict(); + }); + + // Register the Identity services. + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + // Configure Identity to use the same JWT claims as OpenIddict instead + // of the legacy WS-Federation claims it uses by default (ClaimTypes), + // which saves you from doing the mapping in your authorization controller. + services.Configure(options => + { + options.ClaimsIdentity.UserNameClaimType = Claims.Name; + options.ClaimsIdentity.UserIdClaimType = Claims.Subject; + options.ClaimsIdentity.RoleClaimType = Claims.Role; + options.ClaimsIdentity.EmailClaimType = Claims.Email; + }); + + // OpenIddict offers native integration with Quartz.NET to perform scheduled tasks + // (like pruning orphaned authorizations/tokens from the database) at regular intervals. + services.AddQuartz(options => + { + options.UseMicrosoftDependencyInjectionJobFactory(); + options.UseSimpleTypeLoader(); + options.UseInMemoryStore(); + }); - // Register the entity sets needed by OpenIddict. - // Note: use the generic overload if you need - // to replace the default OpenIddict entities. - options.UseOpenIddict(); - }); + // Register the Quartz.NET service and configure it to block shutdown until jobs are complete. + services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true); - // Register the Identity services. - services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); + services.AddOpenIddict() - // Configure Identity to use the same JWT claims as OpenIddict instead - // of the legacy WS-Federation claims it uses by default (ClaimTypes), - // which saves you from doing the mapping in your authorization controller. - services.Configure(options => + // Register the OpenIddict core components. + .AddCore(options => { - options.ClaimsIdentity.UserNameClaimType = Claims.Name; - options.ClaimsIdentity.UserIdClaimType = Claims.Subject; - options.ClaimsIdentity.RoleClaimType = Claims.Role; - options.ClaimsIdentity.EmailClaimType = Claims.Email; - }); - - // OpenIddict offers native integration with Quartz.NET to perform scheduled tasks - // (like pruning orphaned authorizations/tokens from the database) at regular intervals. - services.AddQuartz(options => + // Configure OpenIddict to use the Entity Framework Core stores and models. + // Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities. + options.UseEntityFrameworkCore() + .UseDbContext(); + + // Developers who prefer using MongoDB can remove the previous lines + // and configure OpenIddict to use the specified MongoDB database: + // options.UseMongoDb() + // .UseDatabase(new MongoClient().GetDatabase("openiddict")); + + // Enable Quartz.NET integration. + options.UseQuartz(); + }) + + // Register the OpenIddict server components. + .AddServer(options => + { + // Enable the authorization, device, logout, token, userinfo and verification endpoints. + options.SetAuthorizationEndpointUris("/connect/authorize") + .SetDeviceEndpointUris("/connect/device") + .SetLogoutEndpointUris("/connect/logout") + .SetTokenEndpointUris("/connect/token") + .SetUserinfoEndpointUris("/connect/userinfo") + .SetVerificationEndpointUris("/connect/verify"); + + // Note: this sample uses the code, device code, password and refresh token flows, but you + // can enable the other flows if you need to support implicit or client credentials. + options.AllowAuthorizationCodeFlow() + .AllowDeviceCodeFlow() + .AllowPasswordFlow() + .AllowRefreshTokenFlow(); + + // Mark the "email", "profile", "roles" and "demo_api" scopes as supported scopes. + options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "demo_api"); + + // Register the signing and encryption credentials. + options.AddDevelopmentEncryptionCertificate() + .AddDevelopmentSigningCertificate(); + + // Force client applications to use Proof Key for Code Exchange (PKCE). + options.RequireProofKeyForCodeExchange(); + + // Register the ASP.NET Core host and configure the ASP.NET Core-specific options. + options.UseAspNetCore() + .EnableStatusCodePagesIntegration() + .EnableAuthorizationEndpointPassthrough() + .EnableLogoutEndpointPassthrough() + .EnableTokenEndpointPassthrough() + .EnableUserinfoEndpointPassthrough() + .EnableVerificationEndpointPassthrough() + .DisableTransportSecurityRequirement(); // During development, you can disable the HTTPS requirement. + + // Note: if you don't want to specify a client_id when sending + // a token or revocation request, uncomment the following line: + // + // options.AcceptAnonymousClients(); + + // Note: if you want to process authorization and token requests + // that specify non-registered scopes, uncomment the following line: + // + // options.DisableScopeValidation(); + + // Note: if you don't want to use permissions, you can disable + // permission enforcement by uncommenting the following lines: + // + // options.IgnoreEndpointPermissions() + // .IgnoreGrantTypePermissions() + // .IgnoreResponseTypePermissions() + // .IgnoreScopePermissions(); + + // Note: when issuing access tokens used by third-party APIs + // you don't own, you can disable access token encryption: + // + // options.DisableAccessTokenEncryption(); + }) + + // Register the OpenIddict validation components. + .AddValidation(options => { - options.UseMicrosoftDependencyInjectionJobFactory(); - options.UseSimpleTypeLoader(); - options.UseInMemoryStore(); + // Configure the audience accepted by this resource server. + // The value MUST match the audience associated with the + // "demo_api" scope, which is used by ResourceController. + options.AddAudiences("resource_server"); + + // Import the configuration from the local OpenIddict server instance. + options.UseLocalServer(); + + // Register the ASP.NET Core host. + options.UseAspNetCore(); + + // For applications that need immediate access token or authorization + // revocation, the database entry of the received tokens and their + // associated authorizations can be validated for each API call. + // Enabling these options may have a negative impact on performance. + // + // options.EnableAuthorizationEntryValidation(); + // options.EnableTokenEntryValidation(); }); - // Register the Quartz.NET service and configure it to block shutdown until jobs are complete. - services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true); - - services.AddOpenIddict() - - // Register the OpenIddict core components. - .AddCore(options => - { - // Configure OpenIddict to use the Entity Framework Core stores and models. - // Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities. - options.UseEntityFrameworkCore() - .UseDbContext(); - - // Developers who prefer using MongoDB can remove the previous lines - // and configure OpenIddict to use the specified MongoDB database: - // options.UseMongoDb() - // .UseDatabase(new MongoClient().GetDatabase("openiddict")); - - // Enable Quartz.NET integration. - options.UseQuartz(); - }) - - // Register the OpenIddict server components. - .AddServer(options => - { - // Enable the authorization, device, logout, token, userinfo and verification endpoints. - options.SetAuthorizationEndpointUris("/connect/authorize") - .SetDeviceEndpointUris("/connect/device") - .SetLogoutEndpointUris("/connect/logout") - .SetTokenEndpointUris("/connect/token") - .SetUserinfoEndpointUris("/connect/userinfo") - .SetVerificationEndpointUris("/connect/verify"); - - // Note: this sample uses the code, device code, password and refresh token flows, but you - // can enable the other flows if you need to support implicit or client credentials. - options.AllowAuthorizationCodeFlow() - .AllowDeviceCodeFlow() - .AllowPasswordFlow() - .AllowRefreshTokenFlow(); - - // Mark the "email", "profile", "roles" and "demo_api" scopes as supported scopes. - options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "demo_api"); - - // Register the signing and encryption credentials. - options.AddDevelopmentEncryptionCertificate() - .AddDevelopmentSigningCertificate(); - - // Force client applications to use Proof Key for Code Exchange (PKCE). - options.RequireProofKeyForCodeExchange(); - - // Register the ASP.NET Core host and configure the ASP.NET Core-specific options. - options.UseAspNetCore() - .EnableStatusCodePagesIntegration() - .EnableAuthorizationEndpointPassthrough() - .EnableLogoutEndpointPassthrough() - .EnableTokenEndpointPassthrough() - .EnableUserinfoEndpointPassthrough() - .EnableVerificationEndpointPassthrough() - .DisableTransportSecurityRequirement(); // During development, you can disable the HTTPS requirement. - - // Note: if you don't want to specify a client_id when sending - // a token or revocation request, uncomment the following line: - // - // options.AcceptAnonymousClients(); - - // Note: if you want to process authorization and token requests - // that specify non-registered scopes, uncomment the following line: - // - // options.DisableScopeValidation(); - - // Note: if you don't want to use permissions, you can disable - // permission enforcement by uncommenting the following lines: - // - // options.IgnoreEndpointPermissions() - // .IgnoreGrantTypePermissions() - // .IgnoreResponseTypePermissions() - // .IgnoreScopePermissions(); - - // Note: when issuing access tokens used by third-party APIs - // you don't own, you can disable access token encryption: - // - // options.DisableAccessTokenEncryption(); - }) - - // Register the OpenIddict validation components. - .AddValidation(options => - { - // Configure the audience accepted by this resource server. - // The value MUST match the audience associated with the - // "demo_api" scope, which is used by ResourceController. - options.AddAudiences("resource_server"); - - // Import the configuration from the local OpenIddict server instance. - options.UseLocalServer(); - - // Register the ASP.NET Core host. - options.UseAspNetCore(); - - // For applications that need immediate access token or authorization - // revocation, the database entry of the received tokens and their - // associated authorizations can be validated for each API call. - // Enabling these options may have a negative impact on performance. - // - // options.EnableAuthorizationEntryValidation(); - // options.EnableTokenEntryValidation(); - }); - - services.AddTransient(); - services.AddTransient(); - - // Register the worker responsible of seeding the database with the sample clients. - // Note: in a real world application, this step should be part of a setup script. - services.AddHostedService(); - } - - public void Configure(IApplicationBuilder app) - { - app.UseDeveloperExceptionPage(); + services.AddTransient(); + services.AddTransient(); + + // Register the worker responsible of seeding the database with the sample clients. + // Note: in a real world application, this step should be part of a setup script. + services.AddHostedService(); + } - app.UseStaticFiles(); + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); - app.UseStatusCodePagesWithReExecute("/error"); + app.UseStaticFiles(); - app.UseRouting(); + app.UseStatusCodePagesWithReExecute("/error"); - app.UseRequestLocalization(options => - { - options.AddSupportedCultures("en-US", "fr-FR"); - options.AddSupportedUICultures("en-US", "fr-FR"); - options.SetDefaultCulture("en-US"); - }); + app.UseRouting(); + + app.UseRequestLocalization(options => + { + options.AddSupportedCultures("en-US", "fr-FR"); + options.AddSupportedUICultures("en-US", "fr-FR"); + options.SetDefaultCulture("en-US"); + }); - app.UseAuthentication(); - app.UseAuthorization(); + app.UseAuthentication(); + app.UseAuthorization(); - app.UseEndpoints(options => options.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}")); - } + app.UseEndpoints(options => options.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}")); } } diff --git a/samples/Mvc.Server/ViewModels/Account/ExternalLoginConfirmationViewModel.cs b/samples/Mvc.Server/ViewModels/Account/ExternalLoginConfirmationViewModel.cs index 72715e94..3ef5483c 100644 --- a/samples/Mvc.Server/ViewModels/Account/ExternalLoginConfirmationViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Account/ExternalLoginConfirmationViewModel.cs @@ -1,11 +1,10 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Account +namespace Mvc.Server.ViewModels.Account; + +public class ExternalLoginConfirmationViewModel { - public class ExternalLoginConfirmationViewModel - { - [Required] - [EmailAddress] - public string Email { get; set; } - } + [Required] + [EmailAddress] + public string Email { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Account/ForgotPasswordViewModel.cs b/samples/Mvc.Server/ViewModels/Account/ForgotPasswordViewModel.cs index c6b5c091..9c7080b1 100644 --- a/samples/Mvc.Server/ViewModels/Account/ForgotPasswordViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Account/ForgotPasswordViewModel.cs @@ -1,11 +1,10 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Account +namespace Mvc.Server.ViewModels.Account; + +public class ForgotPasswordViewModel { - public class ForgotPasswordViewModel - { - [Required] - [EmailAddress] - public string Email { get; set; } - } + [Required] + [EmailAddress] + public string Email { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Account/LoginViewModel.cs b/samples/Mvc.Server/ViewModels/Account/LoginViewModel.cs index 18dcd38a..8b178672 100644 --- a/samples/Mvc.Server/ViewModels/Account/LoginViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Account/LoginViewModel.cs @@ -1,18 +1,17 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Account +namespace Mvc.Server.ViewModels.Account; + +public class LoginViewModel { - public class LoginViewModel - { - [Required] - [EmailAddress] - public string Email { get; set; } + [Required] + [EmailAddress] + public string Email { get; set; } - [Required] - [DataType(DataType.Password)] - public string Password { get; set; } + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } - [Display(Name = "Remember me?")] - public bool RememberMe { get; set; } - } + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Account/RegisterViewModel.cs b/samples/Mvc.Server/ViewModels/Account/RegisterViewModel.cs index 9f98dd77..2ddd989d 100644 --- a/samples/Mvc.Server/ViewModels/Account/RegisterViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Account/RegisterViewModel.cs @@ -1,23 +1,22 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Account +namespace Mvc.Server.ViewModels.Account; + +public class RegisterViewModel { - public class RegisterViewModel - { - [Required] - [EmailAddress] - [Display(Name = "Email")] - public string Email { get; set; } + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string Email { get; set; } - [Required] - [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] - [DataType(DataType.Password)] - [Display(Name = "Password")] - public string Password { get; set; } + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } - [DataType(DataType.Password)] - [Display(Name = "Confirm password")] - [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] - public string ConfirmPassword { get; set; } - } + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Account/ResetPasswordViewModel.cs b/samples/Mvc.Server/ViewModels/Account/ResetPasswordViewModel.cs index 863649a0..84bedccb 100644 --- a/samples/Mvc.Server/ViewModels/Account/ResetPasswordViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Account/ResetPasswordViewModel.cs @@ -1,23 +1,22 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Account +namespace Mvc.Server.ViewModels.Account; + +public class ResetPasswordViewModel { - public class ResetPasswordViewModel - { - [Required] - [EmailAddress] - public string Email { get; set; } + [Required] + [EmailAddress] + public string Email { get; set; } - [Required] - [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] - [DataType(DataType.Password)] - public string Password { get; set; } + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + public string Password { get; set; } - [DataType(DataType.Password)] - [Display(Name = "Confirm password")] - [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] - public string ConfirmPassword { get; set; } + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } - public string Code { get; set; } - } + public string Code { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Account/SendCodeViewModel.cs b/samples/Mvc.Server/ViewModels/Account/SendCodeViewModel.cs index 5419eea7..7525506c 100644 --- a/samples/Mvc.Server/ViewModels/Account/SendCodeViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Account/SendCodeViewModel.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering; -namespace Mvc.Server.ViewModels.Account +namespace Mvc.Server.ViewModels.Account; + +public class SendCodeViewModel { - public class SendCodeViewModel - { - public string SelectedProvider { get; set; } + public string SelectedProvider { get; set; } - public ICollection Providers { get; set; } + public ICollection Providers { get; set; } - public string ReturnUrl { get; set; } + public string ReturnUrl { get; set; } - public bool RememberMe { get; set; } - } + public bool RememberMe { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Account/VerifyCodeViewModel.cs b/samples/Mvc.Server/ViewModels/Account/VerifyCodeViewModel.cs index b12eb9b2..52e37c9a 100644 --- a/samples/Mvc.Server/ViewModels/Account/VerifyCodeViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Account/VerifyCodeViewModel.cs @@ -1,21 +1,20 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Account +namespace Mvc.Server.ViewModels.Account; + +public class VerifyCodeViewModel { - public class VerifyCodeViewModel - { - [Required] - public string Provider { get; set; } + [Required] + public string Provider { get; set; } - [Required] - public string Code { get; set; } + [Required] + public string Code { get; set; } - public string ReturnUrl { get; set; } + public string ReturnUrl { get; set; } - [Display(Name = "Remember this browser?")] - public bool RememberBrowser { get; set; } + [Display(Name = "Remember this browser?")] + public bool RememberBrowser { get; set; } - [Display(Name = "Remember me?")] - public bool RememberMe { get; set; } - } + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Authorization/AuthorizeViewModel.cs b/samples/Mvc.Server/ViewModels/Authorization/AuthorizeViewModel.cs index 722d3ade..7cf90736 100644 --- a/samples/Mvc.Server/ViewModels/Authorization/AuthorizeViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Authorization/AuthorizeViewModel.cs @@ -1,13 +1,12 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Authorization +namespace Mvc.Server.ViewModels.Authorization; + +public class AuthorizeViewModel { - public class AuthorizeViewModel - { - [Display(Name = "Application")] - public string ApplicationName { get; set; } + [Display(Name = "Application")] + public string ApplicationName { get; set; } - [Display(Name = "Scope")] - public string Scope { get; set; } - } + [Display(Name = "Scope")] + public string Scope { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Authorization/VerifyViewModel.cs b/samples/Mvc.Server/ViewModels/Authorization/VerifyViewModel.cs index 0a48a0e6..7a9db5aa 100644 --- a/samples/Mvc.Server/ViewModels/Authorization/VerifyViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Authorization/VerifyViewModel.cs @@ -3,24 +3,23 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using OpenIddict.Abstractions; -namespace Mvc.Server.ViewModels.Authorization +namespace Mvc.Server.ViewModels.Authorization; + +public class VerifyViewModel { - public class VerifyViewModel - { - [Display(Name = "Application")] - public string ApplicationName { get; set; } + [Display(Name = "Application")] + public string ApplicationName { get; set; } - [BindNever, Display(Name = "Error")] - public string Error { get; set; } + [BindNever, Display(Name = "Error")] + public string Error { get; set; } - [BindNever, Display(Name = "Error description")] - public string ErrorDescription { get; set; } + [BindNever, Display(Name = "Error description")] + public string ErrorDescription { get; set; } - [Display(Name = "Scope")] - public string Scope { get; set; } + [Display(Name = "Scope")] + public string Scope { get; set; } - [FromQuery(Name = OpenIddictConstants.Parameters.UserCode)] - [Display(Name = "User code")] - public string UserCode { get; set; } - } + [FromQuery(Name = OpenIddictConstants.Parameters.UserCode)] + [Display(Name = "User code")] + public string UserCode { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Manage/AddPhoneNumberViewModel.cs b/samples/Mvc.Server/ViewModels/Manage/AddPhoneNumberViewModel.cs index d1c70f2c..2bddb4d1 100644 --- a/samples/Mvc.Server/ViewModels/Manage/AddPhoneNumberViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Manage/AddPhoneNumberViewModel.cs @@ -1,12 +1,11 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Manage +namespace Mvc.Server.ViewModels.Manage; + +public class AddPhoneNumberViewModel { - public class AddPhoneNumberViewModel - { - [Required] - [Phone] - [Display(Name = "Phone number")] - public string PhoneNumber { get; set; } - } + [Required] + [Phone] + [Display(Name = "Phone number")] + public string PhoneNumber { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Manage/ChangePasswordViewModel.cs b/samples/Mvc.Server/ViewModels/Manage/ChangePasswordViewModel.cs index 83d56483..6e2691f8 100644 --- a/samples/Mvc.Server/ViewModels/Manage/ChangePasswordViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Manage/ChangePasswordViewModel.cs @@ -1,23 +1,22 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Manage +namespace Mvc.Server.ViewModels.Manage; + +public class ChangePasswordViewModel { - public class ChangePasswordViewModel - { - [Required] - [DataType(DataType.Password)] - [Display(Name = "Current password")] - public string OldPassword { get; set; } + [Required] + [DataType(DataType.Password)] + [Display(Name = "Current password")] + public string OldPassword { get; set; } - [Required] - [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] - [DataType(DataType.Password)] - [Display(Name = "New password")] - public string NewPassword { get; set; } + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } - [DataType(DataType.Password)] - [Display(Name = "Confirm new password")] - [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] - public string ConfirmPassword { get; set; } - } + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Manage/ConfigureTwoFactorViewModel.cs b/samples/Mvc.Server/ViewModels/Manage/ConfigureTwoFactorViewModel.cs index d5f57116..c33a6375 100644 --- a/samples/Mvc.Server/ViewModels/Manage/ConfigureTwoFactorViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Manage/ConfigureTwoFactorViewModel.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering; -namespace Mvc.Server.ViewModels.Manage +namespace Mvc.Server.ViewModels.Manage; + +public class ConfigureTwoFactorViewModel { - public class ConfigureTwoFactorViewModel - { - public string SelectedProvider { get; set; } + public string SelectedProvider { get; set; } - public ICollection Providers { get; set; } - } + public ICollection Providers { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Manage/FactorViewModel.cs b/samples/Mvc.Server/ViewModels/Manage/FactorViewModel.cs index 38e4fe73..dc190ceb 100644 --- a/samples/Mvc.Server/ViewModels/Manage/FactorViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Manage/FactorViewModel.cs @@ -1,7 +1,6 @@ -namespace Mvc.Server.ViewModels.Manage +namespace Mvc.Server.ViewModels.Manage; + +public class FactorViewModel { - public class FactorViewModel - { - public string Purpose { get; set; } - } + public string Purpose { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Manage/IndexViewModel.cs b/samples/Mvc.Server/ViewModels/Manage/IndexViewModel.cs index f9ad95ca..0af92151 100644 --- a/samples/Mvc.Server/ViewModels/Manage/IndexViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Manage/IndexViewModel.cs @@ -1,18 +1,17 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Identity; -namespace Mvc.Server.ViewModels.Manage +namespace Mvc.Server.ViewModels.Manage; + +public class IndexViewModel { - public class IndexViewModel - { - public bool HasPassword { get; set; } + public bool HasPassword { get; set; } - public IList Logins { get; set; } + public IList Logins { get; set; } - public string PhoneNumber { get; set; } + public string PhoneNumber { get; set; } - public bool TwoFactor { get; set; } + public bool TwoFactor { get; set; } - public bool BrowserRemembered { get; set; } - } + public bool BrowserRemembered { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Manage/ManageLoginsViewModel.cs b/samples/Mvc.Server/ViewModels/Manage/ManageLoginsViewModel.cs index 8e1adfb4..f49acc45 100644 --- a/samples/Mvc.Server/ViewModels/Manage/ManageLoginsViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Manage/ManageLoginsViewModel.cs @@ -2,12 +2,11 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; -namespace Mvc.Server.ViewModels.Manage +namespace Mvc.Server.ViewModels.Manage; + +public class ManageLoginsViewModel { - public class ManageLoginsViewModel - { - public IList CurrentLogins { get; set; } + public IList CurrentLogins { get; set; } - public IList OtherLogins { get; set; } - } + public IList OtherLogins { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Manage/RemoveLoginViewModel.cs b/samples/Mvc.Server/ViewModels/Manage/RemoveLoginViewModel.cs index 78f03085..a784f759 100644 --- a/samples/Mvc.Server/ViewModels/Manage/RemoveLoginViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Manage/RemoveLoginViewModel.cs @@ -1,8 +1,7 @@ -namespace Mvc.Server.ViewModels.Manage +namespace Mvc.Server.ViewModels.Manage; + +public class RemoveLoginViewModel { - public class RemoveLoginViewModel - { - public string LoginProvider { get; set; } - public string ProviderKey { get; set; } - } -} \ No newline at end of file + public string LoginProvider { get; set; } + public string ProviderKey { get; set; } +} diff --git a/samples/Mvc.Server/ViewModels/Manage/SetPasswordViewModel.cs b/samples/Mvc.Server/ViewModels/Manage/SetPasswordViewModel.cs index 944eed46..ec866186 100644 --- a/samples/Mvc.Server/ViewModels/Manage/SetPasswordViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Manage/SetPasswordViewModel.cs @@ -1,18 +1,17 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Manage +namespace Mvc.Server.ViewModels.Manage; + +public class SetPasswordViewModel { - public class SetPasswordViewModel - { - [Required] - [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] - [DataType(DataType.Password)] - [Display(Name = "New password")] - public string NewPassword { get; set; } + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } - [DataType(DataType.Password)] - [Display(Name = "Confirm new password")] - [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] - public string ConfirmPassword { get; set; } - } + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Manage/VerifyPhoneNumberViewModel.cs b/samples/Mvc.Server/ViewModels/Manage/VerifyPhoneNumberViewModel.cs index dea0eb17..4254a4ef 100644 --- a/samples/Mvc.Server/ViewModels/Manage/VerifyPhoneNumberViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Manage/VerifyPhoneNumberViewModel.cs @@ -1,15 +1,14 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Manage +namespace Mvc.Server.ViewModels.Manage; + +public class VerifyPhoneNumberViewModel { - public class VerifyPhoneNumberViewModel - { - [Required] - public string Code { get; set; } + [Required] + public string Code { get; set; } - [Required] - [Phone] - [Display(Name = "Phone number")] - public string PhoneNumber { get; set; } - } + [Required] + [Phone] + [Display(Name = "Phone number")] + public string PhoneNumber { get; set; } } diff --git a/samples/Mvc.Server/ViewModels/Shared/ErrorViewModel.cs b/samples/Mvc.Server/ViewModels/Shared/ErrorViewModel.cs index ad317594..62b9851a 100644 --- a/samples/Mvc.Server/ViewModels/Shared/ErrorViewModel.cs +++ b/samples/Mvc.Server/ViewModels/Shared/ErrorViewModel.cs @@ -1,13 +1,12 @@ using System.ComponentModel.DataAnnotations; -namespace Mvc.Server.ViewModels.Shared +namespace Mvc.Server.ViewModels.Shared; + +public class ErrorViewModel { - public class ErrorViewModel - { - [Display(Name = "Error")] - public string Error { get; set; } + [Display(Name = "Error")] + public string Error { get; set; } - [Display(Name = "Description")] - public string ErrorDescription { get; set; } - } + [Display(Name = "Description")] + public string ErrorDescription { get; set; } } diff --git a/samples/Mvc.Server/Worker.cs b/samples/Mvc.Server/Worker.cs index ffb46102..3b75a7b5 100644 --- a/samples/Mvc.Server/Worker.cs +++ b/samples/Mvc.Server/Worker.cs @@ -8,130 +8,129 @@ using Mvc.Server.Models; using OpenIddict.Abstractions; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace Mvc.Server +namespace Mvc.Server; + +public class Worker : IHostedService { - public class Worker : IHostedService - { - private readonly IServiceProvider _serviceProvider; + private readonly IServiceProvider _serviceProvider; - public Worker(IServiceProvider serviceProvider) - => _serviceProvider = serviceProvider; + public Worker(IServiceProvider serviceProvider) + => _serviceProvider = serviceProvider; - public async Task StartAsync(CancellationToken cancellationToken) - { - using var scope = _serviceProvider.CreateScope(); + public async Task StartAsync(CancellationToken cancellationToken) + { + using var scope = _serviceProvider.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - await context.Database.EnsureCreatedAsync(cancellationToken); + var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.EnsureCreatedAsync(cancellationToken); - await RegisterApplicationsAsync(scope.ServiceProvider); - await RegisterScopesAsync(scope.ServiceProvider); + await RegisterApplicationsAsync(scope.ServiceProvider); + await RegisterScopesAsync(scope.ServiceProvider); - static async Task RegisterApplicationsAsync(IServiceProvider provider) - { - var manager = provider.GetRequiredService(); + static async Task RegisterApplicationsAsync(IServiceProvider provider) + { + var manager = provider.GetRequiredService(); - if (await manager.FindByClientIdAsync("mvc") is null) + if (await manager.FindByClientIdAsync("mvc") is null) + { + await manager.CreateAsync(new OpenIddictApplicationDescriptor { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + ClientId = "mvc", + ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", + ConsentType = ConsentTypes.Explicit, + DisplayName = "MVC client application", + DisplayNames = + { + [CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente MVC" + }, + PostLogoutRedirectUris = + { + new Uri("https://localhost:44381/signout-callback-oidc") + }, + RedirectUris = + { + new Uri("https://localhost:44381/signin-oidc") + }, + Permissions = + { + Permissions.Endpoints.Authorization, + Permissions.Endpoints.Logout, + Permissions.Endpoints.Token, + Permissions.GrantTypes.AuthorizationCode, + Permissions.GrantTypes.RefreshToken, + Permissions.ResponseTypes.Code, + Permissions.Scopes.Email, + Permissions.Scopes.Profile, + Permissions.Scopes.Roles, + Permissions.Prefixes.Scope + "demo_api" + }, + Requirements = { - ClientId = "mvc", - ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654", - ConsentType = ConsentTypes.Explicit, - DisplayName = "MVC client application", - DisplayNames = - { - [CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente MVC" - }, - PostLogoutRedirectUris = - { - new Uri("https://localhost:44381/signout-callback-oidc") - }, - RedirectUris = - { - new Uri("https://localhost:44381/signin-oidc") - }, - Permissions = - { - Permissions.Endpoints.Authorization, - Permissions.Endpoints.Logout, - Permissions.Endpoints.Token, - Permissions.GrantTypes.AuthorizationCode, - Permissions.GrantTypes.RefreshToken, - Permissions.ResponseTypes.Code, - Permissions.Scopes.Email, - Permissions.Scopes.Profile, - Permissions.Scopes.Roles, - Permissions.Prefixes.Scope + "demo_api" - }, - Requirements = - { - Requirements.Features.ProofKeyForCodeExchange - } - }); - } + Requirements.Features.ProofKeyForCodeExchange + } + }); + } - // To test this sample with Postman, use the following settings: - // - // * Authorization URL: https://localhost:44395/connect/authorize - // * Access token URL: https://localhost:44395/connect/token - // * Client ID: postman - // * Client secret: [blank] (not used with public clients) - // * Scope: openid email profile roles - // * Grant type: authorization code - // * Request access token locally: yes - if (await manager.FindByClientIdAsync("postman") is null) + // To test this sample with Postman, use the following settings: + // + // * Authorization URL: https://localhost:44395/connect/authorize + // * Access token URL: https://localhost:44395/connect/token + // * Client ID: postman + // * Client secret: [blank] (not used with public clients) + // * Scope: openid email profile roles + // * Grant type: authorization code + // * Request access token locally: yes + if (await manager.FindByClientIdAsync("postman") is null) + { + await manager.CreateAsync(new OpenIddictApplicationDescriptor { - await manager.CreateAsync(new OpenIddictApplicationDescriptor + ClientId = "postman", + ConsentType = ConsentTypes.Systematic, + DisplayName = "Postman", + RedirectUris = + { + new Uri("urn:postman") + }, + Permissions = { - ClientId = "postman", - ConsentType = ConsentTypes.Systematic, - DisplayName = "Postman", - RedirectUris = - { - new Uri("urn:postman") - }, - Permissions = - { - Permissions.Endpoints.Authorization, - Permissions.Endpoints.Device, - Permissions.Endpoints.Token, - Permissions.GrantTypes.AuthorizationCode, - Permissions.GrantTypes.DeviceCode, - Permissions.GrantTypes.Password, - Permissions.GrantTypes.RefreshToken, - Permissions.ResponseTypes.Code, - Permissions.Scopes.Email, - Permissions.Scopes.Profile, - Permissions.Scopes.Roles - } - }); - } + Permissions.Endpoints.Authorization, + Permissions.Endpoints.Device, + Permissions.Endpoints.Token, + Permissions.GrantTypes.AuthorizationCode, + Permissions.GrantTypes.DeviceCode, + Permissions.GrantTypes.Password, + Permissions.GrantTypes.RefreshToken, + Permissions.ResponseTypes.Code, + Permissions.Scopes.Email, + Permissions.Scopes.Profile, + Permissions.Scopes.Roles + } + }); } + } - static async Task RegisterScopesAsync(IServiceProvider provider) - { - var manager = provider.GetRequiredService(); + static async Task RegisterScopesAsync(IServiceProvider provider) + { + var manager = provider.GetRequiredService(); - if (await manager.FindByNameAsync("demo_api") is null) + if (await manager.FindByNameAsync("demo_api") is null) + { + await manager.CreateAsync(new OpenIddictScopeDescriptor { - await manager.CreateAsync(new OpenIddictScopeDescriptor + DisplayName = "Demo API access", + DisplayNames = + { + [CultureInfo.GetCultureInfo("fr-FR")] = "Accès à l'API de démo" + }, + Name = "demo_api", + Resources = { - DisplayName = "Demo API access", - DisplayNames = - { - [CultureInfo.GetCultureInfo("fr-FR")] = "Accès à l'API de démo" - }, - Name = "demo_api", - Resources = - { - "resource_server" - } - }); - } + "resource_server" + } + }); } } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } diff --git a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs index b90a5b46..5b2826e5 100644 --- a/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs +++ b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs @@ -3,74 +3,73 @@ using System.Collections.Generic; using System.Linq; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Extensions +namespace OpenIddict.Extensions; + +/// +/// Exposes common helpers used by the OpenIddict assemblies. +/// +internal static class OpenIddictHelpers { /// - /// Exposes common helpers used by the OpenIddict assemblies. + /// Finds the first base type that matches the specified generic type definition. /// - internal static class OpenIddictHelpers - { - /// - /// Finds the first base type that matches the specified generic type definition. - /// - /// The type to introspect. - /// The generic type definition. - /// A instance if the base type was found, null otherwise. - public static Type? FindGenericBaseType(Type type, Type definition) - => FindGenericBaseTypes(type, definition).FirstOrDefault(); + /// The type to introspect. + /// The generic type definition. + /// A instance if the base type was found, null otherwise. + public static Type? FindGenericBaseType(Type type, Type definition) + => FindGenericBaseTypes(type, definition).FirstOrDefault(); - /// - /// Finds all the base types that matches the specified generic type definition. - /// - /// The type to introspect. - /// The generic type definition. - /// A instance if the base type was found, null otherwise. - public static IEnumerable FindGenericBaseTypes(Type type, Type definition) + /// + /// Finds all the base types that matches the specified generic type definition. + /// + /// The type to introspect. + /// The generic type definition. + /// A instance if the base type was found, null otherwise. + public static IEnumerable FindGenericBaseTypes(Type type, Type definition) + { + if (type is null) { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + throw new ArgumentNullException(nameof(type)); + } - if (definition is null) - { - throw new ArgumentNullException(nameof(definition)); - } + if (definition is null) + { + throw new ArgumentNullException(nameof(definition)); + } - if (!definition.IsGenericTypeDefinition) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0263), nameof(definition)); - } + if (!definition.IsGenericTypeDefinition) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0263), nameof(definition)); + } - if (definition.IsInterface) + if (definition.IsInterface) + { + foreach (var contract in type.GetInterfaces()) { - foreach (var contract in type.GetInterfaces()) + if (!contract.IsGenericType && !contract.IsConstructedGenericType) { - if (!contract.IsGenericType && !contract.IsConstructedGenericType) - { - continue; - } + continue; + } - if (contract.GetGenericTypeDefinition() == definition) - { - yield return contract; - } + if (contract.GetGenericTypeDefinition() == definition) + { + yield return contract; } } + } - else + else + { + for (var candidate = type; candidate is not null; candidate = candidate.BaseType) { - for (var candidate = type; candidate is not null; candidate = candidate.BaseType) + if (!candidate.IsGenericType && !candidate.IsConstructedGenericType) { - if (!candidate.IsGenericType && !candidate.IsConstructedGenericType) - { - continue; - } + continue; + } - if (candidate.GetGenericTypeDefinition() == definition) - { - yield return candidate; - } + if (candidate.GetGenericTypeDefinition() == definition) + { + yield return candidate; } } } diff --git a/src/OpenIddict.Abstractions/Caches/IOpenIddictApplicationCache.cs b/src/OpenIddict.Abstractions/Caches/IOpenIddictApplicationCache.cs index 6341a210..e961b181 100644 --- a/src/OpenIddict.Abstractions/Caches/IOpenIddictApplicationCache.cs +++ b/src/OpenIddict.Abstractions/Caches/IOpenIddictApplicationCache.cs @@ -8,66 +8,65 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to cache applications after retrieving them from the store. +/// +/// The type of the Application entity. +public interface IOpenIddictApplicationCache where TApplication : class { /// - /// Provides methods allowing to cache applications after retrieving them from the store. + /// Add the specified application to the cache. /// - /// The type of the Application entity. - public interface IOpenIddictApplicationCache where TApplication : class - { - /// - /// Add the specified application to the cache. - /// - /// The application to add to the cache. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask AddAsync(TApplication application, CancellationToken cancellationToken); + /// The application to add to the cache. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask AddAsync(TApplication application, CancellationToken cancellationToken); - /// - /// Retrieves an application using its client identifier. - /// - /// The client identifier associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client application corresponding to the identifier. - /// - ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves an application using its client identifier. + /// + /// The client identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves an application using its unique identifier. - /// - /// The unique identifier associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client application corresponding to the identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves an application using its unique identifier. + /// + /// The unique identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves all the applications associated with the specified redirect_uri. - /// - /// The redirect_uri associated with the applications. - /// The that can be used to abort the operation. - /// The client applications corresponding to the specified redirect_uri. - IAsyncEnumerable FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken); + /// + /// Retrieves all the applications associated with the specified redirect_uri. + /// + /// The redirect_uri associated with the applications. + /// The that can be used to abort the operation. + /// The client applications corresponding to the specified redirect_uri. + IAsyncEnumerable FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken); - /// - /// Retrieves all the applications associated with the specified redirect_uri. - /// - /// The redirect_uri associated with the applications. - /// The that can be used to abort the operation. - /// The client applications corresponding to the specified redirect_uri. - IAsyncEnumerable FindByRedirectUriAsync(string address, CancellationToken cancellationToken); + /// + /// Retrieves all the applications associated with the specified redirect_uri. + /// + /// The redirect_uri associated with the applications. + /// The that can be used to abort the operation. + /// The client applications corresponding to the specified redirect_uri. + IAsyncEnumerable FindByRedirectUriAsync(string address, CancellationToken cancellationToken); - /// - /// Removes the specified application from the cache. - /// - /// The application to remove from the cache. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask RemoveAsync(TApplication application, CancellationToken cancellationToken); - } + /// + /// Removes the specified application from the cache. + /// + /// The application to remove from the cache. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask RemoveAsync(TApplication application, CancellationToken cancellationToken); } diff --git a/src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs b/src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs index e78a9150..5af53f6b 100644 --- a/src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs +++ b/src/OpenIddict.Abstractions/Caches/IOpenIddictAuthorizationCache.cs @@ -9,102 +9,101 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to cache authorizations after retrieving them from the store. +/// +/// The type of the Authorization entity. +public interface IOpenIddictAuthorizationCache where TAuthorization : class { /// - /// Provides methods allowing to cache authorizations after retrieving them from the store. + /// Add the specified authorization to the cache. /// - /// The type of the Authorization entity. - public interface IOpenIddictAuthorizationCache where TAuthorization : class - { - /// - /// Add the specified authorization to the cache. - /// - /// The authorization to add to the cache. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask AddAsync(TAuthorization authorization, CancellationToken cancellationToken); + /// The authorization to add to the cache. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask AddAsync(TAuthorization authorization, CancellationToken cancellationToken); - /// - /// Retrieves the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); + /// + /// Retrieves the authorizations corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the subject/client. + IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync(string subject, string client, string status, CancellationToken cancellationToken); + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + IAsyncEnumerable FindAsync(string subject, string client, string status, CancellationToken cancellationToken); - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, string status, - string type, CancellationToken cancellationToken); + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, string status, + string type, CancellationToken cancellationToken); - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The minimal scopes associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, string status, - string type, ImmutableArray scopes, CancellationToken cancellationToken); + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The minimal scopes associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, string status, + string type, ImmutableArray scopes, CancellationToken cancellationToken); - /// - /// Retrieves the list of authorizations corresponding to the specified application identifier. - /// - /// The application identifier associated with the authorizations. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the specified application. - IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves the list of authorizations corresponding to the specified application identifier. + /// + /// The application identifier associated with the authorizations. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the specified application. + IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves an authorization using its unique identifier. - /// - /// The unique identifier associated with the authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization corresponding to the identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves an authorization using its unique identifier. + /// + /// The unique identifier associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorization corresponding to the identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves all the authorizations corresponding to the specified subject. - /// - /// The subject associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the specified subject. - IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken); + /// + /// Retrieves all the authorizations corresponding to the specified subject. + /// + /// The subject associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the specified subject. + IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken); - /// - /// Removes the specified authorization from the cache. - /// - /// The authorization to remove from the cache. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask RemoveAsync(TAuthorization authorization, CancellationToken cancellationToken); - } + /// + /// Removes the specified authorization from the cache. + /// + /// The authorization to remove from the cache. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask RemoveAsync(TAuthorization authorization, CancellationToken cancellationToken); } diff --git a/src/OpenIddict.Abstractions/Caches/IOpenIddictScopeCache.cs b/src/OpenIddict.Abstractions/Caches/IOpenIddictScopeCache.cs index bcb6ca6b..0eb2d79a 100644 --- a/src/OpenIddict.Abstractions/Caches/IOpenIddictScopeCache.cs +++ b/src/OpenIddict.Abstractions/Caches/IOpenIddictScopeCache.cs @@ -9,66 +9,65 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to cache scopes after retrieving them from the store. +/// +/// The type of the Scope entity. +public interface IOpenIddictScopeCache where TScope : class { /// - /// Provides methods allowing to cache scopes after retrieving them from the store. + /// Add the specified scope to the cache. /// - /// The type of the Scope entity. - public interface IOpenIddictScopeCache where TScope : class - { - /// - /// Add the specified scope to the cache. - /// - /// The scope to add to the cache. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask AddAsync(TScope scope, CancellationToken cancellationToken); + /// The scope to add to the cache. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask AddAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Retrieves a scope using its unique identifier. - /// - /// The unique identifier associated with the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scope corresponding to the identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves a scope using its unique identifier. + /// + /// The unique identifier associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves a scope using its name. - /// - /// The name associated with the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scope corresponding to the specified name. - /// - ValueTask FindByNameAsync(string name, CancellationToken cancellationToken); + /// + /// Retrieves a scope using its name. + /// + /// The name associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the specified name. + /// + ValueTask FindByNameAsync(string name, CancellationToken cancellationToken); - /// - /// Retrieves a list of scopes using their name. - /// - /// The names associated with the scopes. - /// The that can be used to abort the operation. - /// The scopes corresponding to the specified names. - IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken); + /// + /// Retrieves a list of scopes using their name. + /// + /// The names associated with the scopes. + /// The that can be used to abort the operation. + /// The scopes corresponding to the specified names. + IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken); - /// - /// Retrieves all the scopes that contain the specified resource. - /// - /// The resource associated with the scopes. - /// The that can be used to abort the operation. - /// The scopes associated with the specified resource. - IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken); + /// + /// Retrieves all the scopes that contain the specified resource. + /// + /// The resource associated with the scopes. + /// The that can be used to abort the operation. + /// The scopes associated with the specified resource. + IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken); - /// - /// Removes the specified scope from the cache. - /// - /// The scope to remove from the cache. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask RemoveAsync(TScope scope, CancellationToken cancellationToken); - } + /// + /// Removes the specified scope from the cache. + /// + /// The scope to remove from the cache. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask RemoveAsync(TScope scope, CancellationToken cancellationToken); } diff --git a/src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs b/src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs index 75ae32c9..976d6ee3 100644 --- a/src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs +++ b/src/OpenIddict.Abstractions/Caches/IOpenIddictTokenCache.cs @@ -8,108 +8,107 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to cache tokens after retrieving them from the store. +/// +/// The type of the Token entity. +public interface IOpenIddictTokenCache where TToken : class { /// - /// Provides methods allowing to cache tokens after retrieving them from the store. + /// Add the specified token to the cache. /// - /// The type of the Token entity. - public interface IOpenIddictTokenCache where TToken : class - { - /// - /// Add the specified token to the cache. - /// - /// The token to add to the cache. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask AddAsync(TToken token, CancellationToken cancellationToken); + /// The token to add to the cache. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask AddAsync(TToken token, CancellationToken cancellationToken); - /// - /// Retrieves the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The tokens corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); + /// + /// Retrieves the tokens corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The that can be used to abort the operation. + /// The tokens corresponding to the subject/client. + IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - IAsyncEnumerable FindAsync(string subject, string client, string status, CancellationToken cancellationToken); + /// + /// Retrieves the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The that can be used to abort the operation. + /// The tokens corresponding to the criteria. + IAsyncEnumerable FindAsync(string subject, string client, string status, CancellationToken cancellationToken); - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken); + /// + /// Retrieves the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The token type. + /// The that can be used to abort the operation. + /// The tokens corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken); - /// - /// Retrieves the list of tokens corresponding to the specified application identifier. - /// - /// The application identifier associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified application. - IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves the list of tokens corresponding to the specified application identifier. + /// + /// The application identifier associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified application. + IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves the list of tokens corresponding to the specified authorization identifier. - /// - /// The authorization identifier associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified authorization. - IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves the list of tokens corresponding to the specified authorization identifier. + /// + /// The authorization identifier associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified authorization. + IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves a token using its unique identifier. - /// - /// The unique identifier associated with the token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the token corresponding to the unique identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves a token using its unique identifier. + /// + /// The unique identifier associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token corresponding to the unique identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves the list of tokens corresponding to the specified reference identifier. - /// Note: the reference identifier may be hashed or encrypted for security reasons. - /// - /// The reference identifier associated with the tokens. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the tokens corresponding to the specified reference identifier. - /// - ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves the list of tokens corresponding to the specified reference identifier. + /// Note: the reference identifier may be hashed or encrypted for security reasons. + /// + /// The reference identifier associated with the tokens. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens corresponding to the specified reference identifier. + /// + ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves the list of tokens corresponding to the specified subject. - /// - /// The subject associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified subject. - IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken); + /// + /// Retrieves the list of tokens corresponding to the specified subject. + /// + /// The subject associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified subject. + IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken); - /// - /// Removes the specified token from the cache. - /// - /// The token to remove from the cache. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask RemoveAsync(TToken token, CancellationToken cancellationToken); - } + /// + /// Removes the specified token from the cache. + /// + /// The token to remove from the cache. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask RemoveAsync(TToken token, CancellationToken cancellationToken); } diff --git a/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs b/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs index b1638f1b..c713f1ce 100644 --- a/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs +++ b/src/OpenIddict.Abstractions/Descriptors/OpenIddictApplicationDescriptor.cs @@ -3,68 +3,67 @@ using System.Collections.Generic; using System.Globalization; using System.Text.Json; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Represents an OpenIddict application descriptor. +/// +public class OpenIddictApplicationDescriptor { /// - /// Represents an OpenIddict application descriptor. + /// Gets or sets the client identifier associated with the application. /// - public class OpenIddictApplicationDescriptor - { - /// - /// Gets or sets the client identifier associated with the application. - /// - public string? ClientId { get; set; } + public string? ClientId { get; set; } - /// - /// Gets or sets the client secret associated with the application. - /// Note: depending on the application manager used when creating it, - /// this property may be hashed or encrypted for security reasons. - /// - public string? ClientSecret { get; set; } + /// + /// Gets or sets the client secret associated with the application. + /// Note: depending on the application manager used when creating it, + /// this property may be hashed or encrypted for security reasons. + /// + public string? ClientSecret { get; set; } - /// - /// Gets or sets the consent type associated with the application. - /// - public string? ConsentType { get; set; } + /// + /// Gets or sets the consent type associated with the application. + /// + public string? ConsentType { get; set; } - /// - /// Gets or sets the display name associated with the application. - /// - public string? DisplayName { get; set; } + /// + /// Gets or sets the display name associated with the application. + /// + public string? DisplayName { get; set; } - /// - /// Gets the localized display names associated with the application. - /// - public Dictionary DisplayNames { get; } = new(); + /// + /// Gets the localized display names associated with the application. + /// + public Dictionary DisplayNames { get; } = new(); - /// - /// Gets the permissions associated with the application. - /// - public HashSet Permissions { get; } = new(StringComparer.Ordinal); + /// + /// Gets the permissions associated with the application. + /// + public HashSet Permissions { get; } = new(StringComparer.Ordinal); - /// - /// Gets the logout callback URLs associated with the application. - /// - public HashSet PostLogoutRedirectUris { get; } = new(); + /// + /// Gets the logout callback URLs associated with the application. + /// + public HashSet PostLogoutRedirectUris { get; } = new(); - /// - /// Gets the additional properties associated with the application. - /// - public Dictionary Properties { get; } = new(StringComparer.Ordinal); + /// + /// Gets the additional properties associated with the application. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); - /// - /// Gets the callback URLs associated with the application. - /// - public HashSet RedirectUris { get; } = new(); + /// + /// Gets the callback URLs associated with the application. + /// + public HashSet RedirectUris { get; } = new(); - /// - /// Gets the requirements associated with the application. - /// - public HashSet Requirements { get; } = new(StringComparer.Ordinal); + /// + /// Gets the requirements associated with the application. + /// + public HashSet Requirements { get; } = new(StringComparer.Ordinal); - /// - /// Gets or sets the application type associated with the application. - /// - public string? Type { get; set; } - } + /// + /// Gets or sets the application type associated with the application. + /// + public string? Type { get; set; } } diff --git a/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs b/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs index 9c038894..19b22233 100644 --- a/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs +++ b/src/OpenIddict.Abstractions/Descriptors/OpenIddictAuthorizationDescriptor.cs @@ -3,52 +3,51 @@ using System.Collections.Generic; using System.Security.Claims; using System.Text.Json; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Represents an OpenIddict authorization descriptor. +/// +public class OpenIddictAuthorizationDescriptor { /// - /// Represents an OpenIddict authorization descriptor. - /// - public class OpenIddictAuthorizationDescriptor - { - /// - /// Gets or sets the application identifier associated with the authorization. - /// - public string? ApplicationId { get; set; } - - /// - /// Gets or sets the creation date associated with the authorization. - /// - public DateTimeOffset? CreationDate { get; set; } - - /// - /// Gets or sets the optional principal associated with the authorization. - /// Note: this property is not stored by the default authorization stores. - /// - public ClaimsPrincipal? Principal { get; set; } - - /// - /// Gets the additional properties associated with the authorization. - /// - public Dictionary Properties { get; } = new(StringComparer.Ordinal); - - /// - /// Gets the scopes associated with the authorization. - /// - public HashSet Scopes { get; } = new(StringComparer.Ordinal); - - /// - /// Gets or sets the status associated with the authorization. - /// - public string? Status { get; set; } - - /// - /// Gets or sets the subject associated with the authorization. - /// - public string? Subject { get; set; } - - /// - /// Gets or sets the type of the authorization. - /// - public string? Type { get; set; } - } + /// Gets or sets the application identifier associated with the authorization. + /// + public string? ApplicationId { get; set; } + + /// + /// Gets or sets the creation date associated with the authorization. + /// + public DateTimeOffset? CreationDate { get; set; } + + /// + /// Gets or sets the optional principal associated with the authorization. + /// Note: this property is not stored by the default authorization stores. + /// + public ClaimsPrincipal? Principal { get; set; } + + /// + /// Gets the additional properties associated with the authorization. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the scopes associated with the authorization. + /// + public HashSet Scopes { get; } = new(StringComparer.Ordinal); + + /// + /// Gets or sets the status associated with the authorization. + /// + public string? Status { get; set; } + + /// + /// Gets or sets the subject associated with the authorization. + /// + public string? Subject { get; set; } + + /// + /// Gets or sets the type of the authorization. + /// + public string? Type { get; set; } } diff --git a/src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs b/src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs index 69ba8f5b..131ae66d 100644 --- a/src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs +++ b/src/OpenIddict.Abstractions/Descriptors/OpenIddictScopeDescriptor.cs @@ -3,46 +3,45 @@ using System.Collections.Generic; using System.Globalization; using System.Text.Json; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Represents an OpenIddict scope descriptor. +/// +public class OpenIddictScopeDescriptor { /// - /// Represents an OpenIddict scope descriptor. + /// Gets or sets the description associated with the scope. + /// + public string? Description { get; set; } + + /// + /// Gets the localized descriptions associated with the scope. + /// + public Dictionary Descriptions { get; } = new(); + + /// + /// Gets or sets the display name associated with the scope. + /// + public string? DisplayName { get; set; } + + /// + /// Gets the localized display names associated with the scope. + /// + public Dictionary DisplayNames { get; } = new(); + + /// + /// Gets or sets the unique name associated with the scope. + /// + public string? Name { get; set; } + + /// + /// Gets the additional properties associated with the scope. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the resources associated with the scope. /// - public class OpenIddictScopeDescriptor - { - /// - /// Gets or sets the description associated with the scope. - /// - public string? Description { get; set; } - - /// - /// Gets the localized descriptions associated with the scope. - /// - public Dictionary Descriptions { get; } = new(); - - /// - /// Gets or sets the display name associated with the scope. - /// - public string? DisplayName { get; set; } - - /// - /// Gets the localized display names associated with the scope. - /// - public Dictionary DisplayNames { get; } = new(); - - /// - /// Gets or sets the unique name associated with the scope. - /// - public string? Name { get; set; } - - /// - /// Gets the additional properties associated with the scope. - /// - public Dictionary Properties { get; } = new(StringComparer.Ordinal); - - /// - /// Gets the resources associated with the scope. - /// - public HashSet Resources { get; } = new(StringComparer.Ordinal); - } + public HashSet Resources { get; } = new(StringComparer.Ordinal); } diff --git a/src/OpenIddict.Abstractions/Descriptors/OpenIddictTokenDescriptor.cs b/src/OpenIddict.Abstractions/Descriptors/OpenIddictTokenDescriptor.cs index 4c931407..149a75fd 100644 --- a/src/OpenIddict.Abstractions/Descriptors/OpenIddictTokenDescriptor.cs +++ b/src/OpenIddict.Abstractions/Descriptors/OpenIddictTokenDescriptor.cs @@ -3,74 +3,73 @@ using System.Collections.Generic; using System.Security.Claims; using System.Text.Json; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Represents an OpenIddict token descriptor. +/// +public class OpenIddictTokenDescriptor { /// - /// Represents an OpenIddict token descriptor. + /// Gets or sets the application identifier associated with the token. /// - public class OpenIddictTokenDescriptor - { - /// - /// Gets or sets the application identifier associated with the token. - /// - public string? ApplicationId { get; set; } + public string? ApplicationId { get; set; } - /// - /// Gets or sets the authorization identifier associated with the token. - /// - public string? AuthorizationId { get; set; } + /// + /// Gets or sets the authorization identifier associated with the token. + /// + public string? AuthorizationId { get; set; } - /// - /// Gets or sets the creation date associated with the token. - /// - public DateTimeOffset? CreationDate { get; set; } + /// + /// Gets or sets the creation date associated with the token. + /// + public DateTimeOffset? CreationDate { get; set; } - /// - /// Gets or sets the expiration date associated with the token. - /// - public DateTimeOffset? ExpirationDate { get; set; } + /// + /// Gets or sets the expiration date associated with the token. + /// + public DateTimeOffset? ExpirationDate { get; set; } - /// - /// Gets or sets the payload associated with the token. - /// - public string? Payload { get; set; } + /// + /// Gets or sets the payload associated with the token. + /// + public string? Payload { get; set; } - /// - /// Gets or sets the optional principal associated with the token. - /// Note: this property is not stored by the default token stores. - /// - public ClaimsPrincipal? Principal { get; set; } + /// + /// Gets or sets the optional principal associated with the token. + /// Note: this property is not stored by the default token stores. + /// + public ClaimsPrincipal? Principal { get; set; } - /// - /// Gets the additional properties associated with the token. - /// - public Dictionary Properties { get; } = new(StringComparer.Ordinal); + /// + /// Gets the additional properties associated with the token. + /// + public Dictionary Properties { get; } = new(StringComparer.Ordinal); - /// - /// Gets or sets the redemption date associated with the token. - /// - public DateTimeOffset? RedemptionDate { get; set; } + /// + /// Gets or sets the redemption date associated with the token. + /// + public DateTimeOffset? RedemptionDate { get; set; } - /// - /// Gets or sets the reference identifier associated with the token. - /// Note: depending on the application manager used when creating it, - /// this property may be hashed or encrypted for security reasons. - /// - public string? ReferenceId { get; set; } + /// + /// Gets or sets the reference identifier associated with the token. + /// Note: depending on the application manager used when creating it, + /// this property may be hashed or encrypted for security reasons. + /// + public string? ReferenceId { get; set; } - /// - /// Gets or sets the status associated with the token. - /// - public string? Status { get; set; } + /// + /// Gets or sets the status associated with the token. + /// + public string? Status { get; set; } - /// - /// Gets or sets the subject associated with the token. - /// - public string? Subject { get; set; } + /// + /// Gets or sets the subject associated with the token. + /// + public string? Subject { get; set; } - /// - /// Gets or sets the token type. - /// - public string? Type { get; set; } - } + /// + /// Gets or sets the token type. + /// + public string? Type { get; set; } } diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs index 3398b12f..2d9e682a 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs @@ -14,457 +14,456 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to manage the applications stored in the store. +/// Note: this interface is not meant to be implemented by custom managers, +/// that should inherit from the generic OpenIddictApplicationManager class. +/// It is primarily intended to be used by services that cannot easily depend +/// on the generic application manager. The actual application entity type +/// is automatically determined at runtime based on the OpenIddict core options. +/// +public interface IOpenIddictApplicationManager { /// - /// Provides methods allowing to manage the applications stored in the store. - /// Note: this interface is not meant to be implemented by custom managers, - /// that should inherit from the generic OpenIddictApplicationManager class. - /// It is primarily intended to be used by services that cannot easily depend - /// on the generic application manager. The actual application entity type - /// is automatically determined at runtime based on the OpenIddict core options. - /// - public interface IOpenIddictApplicationManager - { - /// - /// Determines the number of applications that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of applications in the database. - /// - ValueTask CountAsync(CancellationToken cancellationToken = default); - - /// - /// Determines the number of applications that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of applications that match the specified query. - /// - ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Creates a new application based on the specified descriptor. - /// Note: the default implementation automatically hashes the client - /// secret before storing it in the database, for security reasons. - /// - /// The application descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the application. - /// - ValueTask CreateAsync(OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Creates a new application. - /// - /// The application to create. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask CreateAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Creates a new application. - /// Note: the default implementation automatically hashes the client - /// secret before storing it in the database, for security reasons. - /// - /// The application to create. - /// The client secret associated with the application, if applicable. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask CreateAsync(object application, string? secret, CancellationToken cancellationToken = default); - - /// - /// Removes an existing application. - /// - /// The application to delete. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask DeleteAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves an application using its client identifier. - /// - /// The client identifier associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client application corresponding to the identifier. - /// - ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken = default); - - /// - /// Retrieves an application using its unique identifier. - /// - /// The unique identifier associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client application corresponding to the identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default); - - /// - /// Retrieves all the applications associated with the specified post_logout_redirect_uri. - /// - /// The post_logout_redirect_uri associated with the applications. - /// The that can be used to abort the operation. - /// The client applications corresponding to the specified post_logout_redirect_uri. - IAsyncEnumerable FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken = default); - - /// - /// Retrieves all the applications associated with the specified redirect_uri. - /// - /// The redirect_uri associated with the applications. - /// The that can be used to abort the operation. - /// The client applications corresponding to the specified redirect_uri. - IAsyncEnumerable FindByRedirectUriAsync(string address, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns the first element. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default); - - /// - /// Retrieves the client identifier associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client identifier associated with the application. - /// - ValueTask GetClientIdAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the client type associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client type of the application (by default, "public"). - /// - ValueTask GetClientTypeAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the consent type associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the consent type of the application (by default, "explicit"). - /// - ValueTask GetConsentTypeAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the display name associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the display name associated with the application. - /// - ValueTask GetDisplayNameAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the localized display names associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the localized display names associated with the application. - /// - ValueTask> GetDisplayNamesAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the unique identifier associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the application. - /// - ValueTask GetIdAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the localized display name associated with an application - /// and corresponding to the current UI culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching localized display name associated with the application. - /// - ValueTask GetLocalizedDisplayNameAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the localized display name associated with an application - /// and corresponding to the specified culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The application. - /// The culture (typically ). - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching localized display name associated with the application. - /// - ValueTask GetLocalizedDisplayNameAsync(object application, CultureInfo culture, CancellationToken cancellationToken = default); - - /// - /// Retrieves the permissions associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the permissions associated with the application. - /// - ValueTask> GetPermissionsAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the logout callback addresses associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the post_logout_redirect_uri associated with the application. - /// - ValueTask> GetPostLogoutRedirectUrisAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the additional properties associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the application. - /// - ValueTask> GetPropertiesAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the callback addresses associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the redirect_uri associated with the application. - /// - ValueTask> GetRedirectUrisAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Retrieves the requirements associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the requirements associated with the application. - /// - ValueTask> GetRequirementsAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Determines whether a given application has the specified client type. - /// - /// The application. - /// The expected client type. - /// The that can be used to abort the operation. - /// true if the application has the specified client type, false otherwise. - ValueTask HasClientTypeAsync(object application, string type, CancellationToken cancellationToken = default); - - /// - /// Determines whether a given application has the specified consent type. - /// - /// The application. - /// The expected consent type. - /// The that can be used to abort the operation. - /// true if the application has the specified consent type, false otherwise. - ValueTask HasConsentTypeAsync(object application, string type, CancellationToken cancellationToken = default); - - /// - /// Determines whether the specified permission has been granted to the application. - /// - /// The application. - /// The permission. - /// The that can be used to abort the operation. - /// true if the application has been granted the specified permission, false otherwise. - ValueTask HasPermissionAsync(object application, string permission, CancellationToken cancellationToken = default); - - /// - /// Determines whether the specified requirement has been enforced for the specified application. - /// - /// The application. - /// The requirement. - /// The that can be used to abort the operation. - /// true if the requirement has been enforced for the specified application, false otherwise. - ValueTask HasRequirementAsync(object application, string requirement, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - int? count = null, int? offset = null, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default); - - /// - /// Populates the specified descriptor using the properties exposed by the application. - /// - /// The descriptor. - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PopulateAsync(OpenIddictApplicationDescriptor descriptor, object application, CancellationToken cancellationToken = default); - - /// - /// Populates the application using the specified descriptor. - /// - /// The application. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PopulateAsync(object application, OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Updates an existing application. - /// - /// The application to update. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask UpdateAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Updates an existing application. - /// - /// The application to update. - /// The descriptor used to update the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask UpdateAsync(object application, OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Updates an existing application and replaces the existing secret. - /// Note: the default implementation automatically hashes the client - /// secret before storing it in the database, for security reasons. - /// - /// The application to update. - /// The client secret associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask UpdateAsync(object application, string secret, CancellationToken cancellationToken = default); - - /// - /// Validates the application to ensure it's in a consistent state. - /// - /// The application. - /// The that can be used to abort the operation. - /// The validation error encountered when validating the application. - IAsyncEnumerable ValidateAsync(object application, CancellationToken cancellationToken = default); - - /// - /// Validates the client_secret associated with an application. - /// - /// The application. - /// The secret that should be compared to the client_secret stored in the database. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns a boolean indicating whether the client secret was valid. - /// - ValueTask ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken = default); - - /// - /// Validates the redirect_uri to ensure it's associated with an application. - /// - /// The application. - /// The address that should be compared to one of the redirect_uri stored in the database. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns a boolean indicating whether the redirect_uri was valid. - /// - ValueTask ValidateRedirectUriAsync(object application, string address, CancellationToken cancellationToken = default); - } + /// Determines the number of applications that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of applications in the database. + /// + ValueTask CountAsync(CancellationToken cancellationToken = default); + + /// + /// Determines the number of applications that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of applications that match the specified query. + /// + ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Creates a new application based on the specified descriptor. + /// Note: the default implementation automatically hashes the client + /// secret before storing it in the database, for security reasons. + /// + /// The application descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the application. + /// + ValueTask CreateAsync(OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Creates a new application. + /// + /// The application to create. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask CreateAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Creates a new application. + /// Note: the default implementation automatically hashes the client + /// secret before storing it in the database, for security reasons. + /// + /// The application to create. + /// The client secret associated with the application, if applicable. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask CreateAsync(object application, string? secret, CancellationToken cancellationToken = default); + + /// + /// Removes an existing application. + /// + /// The application to delete. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask DeleteAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves an application using its client identifier. + /// + /// The client identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken = default); + + /// + /// Retrieves an application using its unique identifier. + /// + /// The unique identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default); + + /// + /// Retrieves all the applications associated with the specified post_logout_redirect_uri. + /// + /// The post_logout_redirect_uri associated with the applications. + /// The that can be used to abort the operation. + /// The client applications corresponding to the specified post_logout_redirect_uri. + IAsyncEnumerable FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken = default); + + /// + /// Retrieves all the applications associated with the specified redirect_uri. + /// + /// The redirect_uri associated with the applications. + /// The that can be used to abort the operation. + /// The client applications corresponding to the specified redirect_uri. + IAsyncEnumerable FindByRedirectUriAsync(string address, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns the first element. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default); + + /// + /// Retrieves the client identifier associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client identifier associated with the application. + /// + ValueTask GetClientIdAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the client type associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client type of the application (by default, "public"). + /// + ValueTask GetClientTypeAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the consent type associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the consent type of the application (by default, "explicit"). + /// + ValueTask GetConsentTypeAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the display name associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the application. + /// + ValueTask GetDisplayNameAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the localized display names associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the localized display names associated with the application. + /// + ValueTask> GetDisplayNamesAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the unique identifier associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the application. + /// + ValueTask GetIdAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the localized display name associated with an application + /// and corresponding to the current UI culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching localized display name associated with the application. + /// + ValueTask GetLocalizedDisplayNameAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the localized display name associated with an application + /// and corresponding to the specified culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The application. + /// The culture (typically ). + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching localized display name associated with the application. + /// + ValueTask GetLocalizedDisplayNameAsync(object application, CultureInfo culture, CancellationToken cancellationToken = default); + + /// + /// Retrieves the permissions associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the permissions associated with the application. + /// + ValueTask> GetPermissionsAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the logout callback addresses associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the post_logout_redirect_uri associated with the application. + /// + ValueTask> GetPostLogoutRedirectUrisAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the additional properties associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the application. + /// + ValueTask> GetPropertiesAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the callback addresses associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the redirect_uri associated with the application. + /// + ValueTask> GetRedirectUrisAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Retrieves the requirements associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the requirements associated with the application. + /// + ValueTask> GetRequirementsAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Determines whether a given application has the specified client type. + /// + /// The application. + /// The expected client type. + /// The that can be used to abort the operation. + /// true if the application has the specified client type, false otherwise. + ValueTask HasClientTypeAsync(object application, string type, CancellationToken cancellationToken = default); + + /// + /// Determines whether a given application has the specified consent type. + /// + /// The application. + /// The expected consent type. + /// The that can be used to abort the operation. + /// true if the application has the specified consent type, false otherwise. + ValueTask HasConsentTypeAsync(object application, string type, CancellationToken cancellationToken = default); + + /// + /// Determines whether the specified permission has been granted to the application. + /// + /// The application. + /// The permission. + /// The that can be used to abort the operation. + /// true if the application has been granted the specified permission, false otherwise. + ValueTask HasPermissionAsync(object application, string permission, CancellationToken cancellationToken = default); + + /// + /// Determines whether the specified requirement has been enforced for the specified application. + /// + /// The application. + /// The requirement. + /// The that can be used to abort the operation. + /// true if the requirement has been enforced for the specified application, false otherwise. + ValueTask HasRequirementAsync(object application, string requirement, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + int? count = null, int? offset = null, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default); + + /// + /// Populates the specified descriptor using the properties exposed by the application. + /// + /// The descriptor. + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PopulateAsync(OpenIddictApplicationDescriptor descriptor, object application, CancellationToken cancellationToken = default); + + /// + /// Populates the application using the specified descriptor. + /// + /// The application. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PopulateAsync(object application, OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Updates an existing application. + /// + /// The application to update. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask UpdateAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Updates an existing application. + /// + /// The application to update. + /// The descriptor used to update the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask UpdateAsync(object application, OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Updates an existing application and replaces the existing secret. + /// Note: the default implementation automatically hashes the client + /// secret before storing it in the database, for security reasons. + /// + /// The application to update. + /// The client secret associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask UpdateAsync(object application, string secret, CancellationToken cancellationToken = default); + + /// + /// Validates the application to ensure it's in a consistent state. + /// + /// The application. + /// The that can be used to abort the operation. + /// The validation error encountered when validating the application. + IAsyncEnumerable ValidateAsync(object application, CancellationToken cancellationToken = default); + + /// + /// Validates the client_secret associated with an application. + /// + /// The application. + /// The secret that should be compared to the client_secret stored in the database. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns a boolean indicating whether the client secret was valid. + /// + ValueTask ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken = default); + + /// + /// Validates the redirect_uri to ensure it's associated with an application. + /// + /// The application. + /// The address that should be compared to one of the redirect_uri stored in the database. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns a boolean indicating whether the redirect_uri was valid. + /// + ValueTask ValidateRedirectUriAsync(object application, string address, CancellationToken cancellationToken = default); } diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs index 84ea62e7..94ae115c 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs @@ -14,412 +14,411 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to manage the authorizations stored in the store. +/// Note: this interface is not meant to be implemented by custom managers, +/// that should inherit from the generic OpenIddictAuthorizationManager class. +/// It is primarily intended to be used by services that cannot easily depend +/// on the generic authorization manager. The actual authorization entity type +/// is automatically determined at runtime based on the OpenIddict core options. +/// +public interface IOpenIddictAuthorizationManager { /// - /// Provides methods allowing to manage the authorizations stored in the store. - /// Note: this interface is not meant to be implemented by custom managers, - /// that should inherit from the generic OpenIddictAuthorizationManager class. - /// It is primarily intended to be used by services that cannot easily depend - /// on the generic authorization manager. The actual authorization entity type - /// is automatically determined at runtime based on the OpenIddict core options. - /// - public interface IOpenIddictAuthorizationManager - { - /// - /// Determines the number of authorizations that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of authorizations in the database. - /// - ValueTask CountAsync(CancellationToken cancellationToken = default); - - /// - /// Determines the number of authorizations that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of authorizations that match the specified query. - /// - ValueTask CountAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Creates a new permanent authorization based on the specified parameters. - /// - /// The principal associated with the authorization. - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization type. - /// The minimal scopes associated with the authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. - /// - ValueTask CreateAsync( - ClaimsPrincipal principal, string subject, string client, - string type, ImmutableArray scopes, CancellationToken cancellationToken = default); - - /// - /// Creates a new authorization based on the specified descriptor. - /// - /// The authorization descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. - /// - ValueTask CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Creates a new authorization. - /// - /// The application to create. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask CreateAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Removes an existing authorization. - /// - /// The authorization to delete. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask DeleteAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Retrieves the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken = default); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken = default); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken = default); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The minimal scopes associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, string status, - string type, ImmutableArray scopes, CancellationToken cancellationToken = default); - - /// - /// Retrieves the list of authorizations corresponding to the specified application identifier. - /// - /// The application identifier associated with the authorizations. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the specified application. - IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default); - - /// - /// Retrieves an authorization using its unique identifier. - /// - /// The unique identifier associated with the authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization corresponding to the identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default); - - /// - /// Retrieves all the authorizations corresponding to the specified subject. - /// - /// The subject associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the specified subject. - IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken = default); - - /// - /// Retrieves the optional application identifier associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the application identifier associated with the authorization. - /// - ValueTask GetApplicationIdAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns the first element. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default); - - /// - /// Retrieves the creation date associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the creation date associated with the specified authorization. - /// - ValueTask GetCreationDateAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Retrieves the unique identifier associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the authorization. - /// - ValueTask GetIdAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Retrieves the additional properties associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the authorization. - /// - ValueTask> GetPropertiesAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Retrieves the scopes associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scopes associated with the specified authorization. - /// - ValueTask> GetScopesAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Retrieves the status associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the status associated with the specified authorization. - /// - ValueTask GetStatusAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Retrieves the subject associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the subject associated with the specified authorization. - /// - ValueTask GetSubjectAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Retrieves the type associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the type associated with the specified authorization. - /// - ValueTask GetTypeAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Determines whether the specified scopes are included in the authorization. - /// - /// The authorization. - /// The scopes. - /// The that can be used to abort the operation. - /// true if the scopes are included in the authorization, false otherwise. - ValueTask HasScopesAsync(object authorization, ImmutableArray scopes, CancellationToken cancellationToken = default); - - /// - /// Determines whether a given authorization has the specified status. - /// - /// The authorization. - /// The expected status. - /// The that can be used to abort the operation. - /// true if the authorization has the specified status, false otherwise. - ValueTask HasStatusAsync(object authorization, string status, CancellationToken cancellationToken = default); - - /// - /// Determines whether a given authorization has the specified type. - /// - /// The authorization. - /// The expected type. - /// The that can be used to abort the operation. - /// true if the authorization has the specified type, false otherwise. - ValueTask HasTypeAsync(object authorization, string type, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - int? count = null, int? offset = null, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default); - - /// - /// Populates the specified descriptor using the properties exposed by the authorization. - /// - /// The descriptor. - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PopulateAsync(OpenIddictAuthorizationDescriptor descriptor, object authorization, CancellationToken cancellationToken = default); - - /// - /// Populates the authorization using the specified descriptor. - /// - /// The authorization. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PopulateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached. - /// Only authorizations created before the specified are removed. - /// - /// - /// To ensure ad-hoc authorizations that no longer have any valid/non-expired token - /// attached are correctly removed, the tokens should always be pruned first. - /// - /// The date before which authorizations are not pruned. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default); - - /// - /// Tries to revoke an authorization. - /// - /// The authorization to revoke. - /// The that can be used to abort the operation. - /// true if the authorization was successfully revoked, false otherwise. - ValueTask TryRevokeAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Updates an existing authorization. - /// - /// The authorization to update. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask UpdateAsync(object authorization, CancellationToken cancellationToken = default); - - /// - /// Updates an existing authorization. - /// - /// The authorization to update. - /// The descriptor used to update the authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask UpdateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Validates the authorization to ensure it's in a consistent state. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// The validation error encountered when validating the authorization. - IAsyncEnumerable ValidateAsync(object authorization, CancellationToken cancellationToken = default); - } + /// Determines the number of authorizations that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of authorizations in the database. + /// + ValueTask CountAsync(CancellationToken cancellationToken = default); + + /// + /// Determines the number of authorizations that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of authorizations that match the specified query. + /// + ValueTask CountAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Creates a new permanent authorization based on the specified parameters. + /// + /// The principal associated with the authorization. + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization type. + /// The minimal scopes associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. + /// + ValueTask CreateAsync( + ClaimsPrincipal principal, string subject, string client, + string type, ImmutableArray scopes, CancellationToken cancellationToken = default); + + /// + /// Creates a new authorization based on the specified descriptor. + /// + /// The authorization descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. + /// + ValueTask CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Creates a new authorization. + /// + /// The application to create. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask CreateAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Removes an existing authorization. + /// + /// The authorization to delete. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask DeleteAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Retrieves the authorizations corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the subject/client. + IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken = default); + + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken = default); + + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken = default); + + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The minimal scopes associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, string status, + string type, ImmutableArray scopes, CancellationToken cancellationToken = default); + + /// + /// Retrieves the list of authorizations corresponding to the specified application identifier. + /// + /// The application identifier associated with the authorizations. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the specified application. + IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default); + + /// + /// Retrieves an authorization using its unique identifier. + /// + /// The unique identifier associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorization corresponding to the identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default); + + /// + /// Retrieves all the authorizations corresponding to the specified subject. + /// + /// The subject associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the specified subject. + IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken = default); + + /// + /// Retrieves the optional application identifier associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the application identifier associated with the authorization. + /// + ValueTask GetApplicationIdAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns the first element. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default); + + /// + /// Retrieves the creation date associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the creation date associated with the specified authorization. + /// + ValueTask GetCreationDateAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Retrieves the unique identifier associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the authorization. + /// + ValueTask GetIdAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Retrieves the additional properties associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the authorization. + /// + ValueTask> GetPropertiesAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Retrieves the scopes associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scopes associated with the specified authorization. + /// + ValueTask> GetScopesAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Retrieves the status associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the status associated with the specified authorization. + /// + ValueTask GetStatusAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Retrieves the subject associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the subject associated with the specified authorization. + /// + ValueTask GetSubjectAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Retrieves the type associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the type associated with the specified authorization. + /// + ValueTask GetTypeAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Determines whether the specified scopes are included in the authorization. + /// + /// The authorization. + /// The scopes. + /// The that can be used to abort the operation. + /// true if the scopes are included in the authorization, false otherwise. + ValueTask HasScopesAsync(object authorization, ImmutableArray scopes, CancellationToken cancellationToken = default); + + /// + /// Determines whether a given authorization has the specified status. + /// + /// The authorization. + /// The expected status. + /// The that can be used to abort the operation. + /// true if the authorization has the specified status, false otherwise. + ValueTask HasStatusAsync(object authorization, string status, CancellationToken cancellationToken = default); + + /// + /// Determines whether a given authorization has the specified type. + /// + /// The authorization. + /// The expected type. + /// The that can be used to abort the operation. + /// true if the authorization has the specified type, false otherwise. + ValueTask HasTypeAsync(object authorization, string type, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + int? count = null, int? offset = null, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default); + + /// + /// Populates the specified descriptor using the properties exposed by the authorization. + /// + /// The descriptor. + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PopulateAsync(OpenIddictAuthorizationDescriptor descriptor, object authorization, CancellationToken cancellationToken = default); + + /// + /// Populates the authorization using the specified descriptor. + /// + /// The authorization. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PopulateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached. + /// Only authorizations created before the specified are removed. + /// + /// + /// To ensure ad-hoc authorizations that no longer have any valid/non-expired token + /// attached are correctly removed, the tokens should always be pruned first. + /// + /// The date before which authorizations are not pruned. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default); + + /// + /// Tries to revoke an authorization. + /// + /// The authorization to revoke. + /// The that can be used to abort the operation. + /// true if the authorization was successfully revoked, false otherwise. + ValueTask TryRevokeAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Updates an existing authorization. + /// + /// The authorization to update. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask UpdateAsync(object authorization, CancellationToken cancellationToken = default); + + /// + /// Updates an existing authorization. + /// + /// The authorization to update. + /// The descriptor used to update the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask UpdateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Validates the authorization to ensure it's in a consistent state. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// The validation error encountered when validating the authorization. + IAsyncEnumerable ValidateAsync(object authorization, CancellationToken cancellationToken = default); } diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs index 8e5d1ec2..c430d041 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs @@ -14,369 +14,368 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to manage the scopes stored in the store. +/// Note: this interface is not meant to be implemented by custom managers, +/// that should inherit from the generic OpenIddictScopeManager class. +/// It is primarily intended to be used by services that cannot easily +/// depend on the generic scope manager. The actual scope entity type is +/// automatically determined at runtime based on the OpenIddict core options. +/// +public interface IOpenIddictScopeManager { /// - /// Provides methods allowing to manage the scopes stored in the store. - /// Note: this interface is not meant to be implemented by custom managers, - /// that should inherit from the generic OpenIddictScopeManager class. - /// It is primarily intended to be used by services that cannot easily - /// depend on the generic scope manager. The actual scope entity type is - /// automatically determined at runtime based on the OpenIddict core options. - /// - public interface IOpenIddictScopeManager - { - /// - /// Determines the number of scopes that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of scopes in the database. - /// - ValueTask CountAsync(CancellationToken cancellationToken = default); - - /// - /// Determines the number of scopes that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of scopes that match the specified query. - /// - ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Creates a new scope based on the specified descriptor. - /// - /// The scope descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the scope. - /// - ValueTask CreateAsync(OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Creates a new scope. - /// - /// The scope to create. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask CreateAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Removes an existing scope. - /// - /// The scope to delete. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask DeleteAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves a scope using its unique identifier. - /// - /// The unique identifier associated with the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scope corresponding to the identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default); - - /// - /// Retrieves a scope using its name. - /// - /// The name associated with the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scope corresponding to the specified name. - /// - ValueTask FindByNameAsync(string name, CancellationToken cancellationToken = default); - - /// - /// Retrieves a list of scopes using their name. - /// - /// The names associated with the scopes. - /// The that can be used to abort the operation. - /// The scopes corresponding to the specified names. - IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken = default); - - /// - /// Retrieves all the scopes that contain the specified resource. - /// - /// The resource associated with the scopes. - /// The that can be used to abort the operation. - /// The scopes associated with the specified resource. - IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns the first element. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default); - - /// - /// Retrieves the description associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the description associated with the specified scope. - /// - ValueTask GetDescriptionAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves the localized descriptions associated with an scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the localized descriptions associated with the scope. - /// - ValueTask> GetDescriptionsAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves the display name associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the display name associated with the scope. - /// - ValueTask GetDisplayNameAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves the localized display names associated with an scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the localized display names associated with the scope. - /// - ValueTask> GetDisplayNamesAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves the unique identifier associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the scope. - /// - ValueTask GetIdAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves the localized description associated with an scope - /// and corresponding to the current UI culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching localized description associated with the scope. - /// - ValueTask GetLocalizedDescriptionAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves the localized description associated with an scope - /// and corresponding to the specified culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The scope. - /// The culture (typically ). - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching localized description associated with the scope. - /// - ValueTask GetLocalizedDescriptionAsync(object scope, CultureInfo culture, CancellationToken cancellationToken = default); - - /// - /// Retrieves the localized display name associated with an scope - /// and corresponding to the current UI culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the display name associated with the scope. - /// - ValueTask GetLocalizedDisplayNameAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves the localized display name associated with an scope - /// and corresponding to the specified culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The scope. - /// The culture (typically ). - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the display name associated with the scope. - /// - ValueTask GetLocalizedDisplayNameAsync(object scope, CultureInfo culture, CancellationToken cancellationToken = default); - - /// - /// Retrieves the name associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the name associated with the specified scope. - /// - ValueTask GetNameAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves the additional properties associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the scope. - /// - ValueTask> GetPropertiesAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Retrieves the resources associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the resources associated with the scope. - /// - ValueTask> GetResourcesAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - int? count = null, int? offset = null, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default); - - /// - /// Lists all the resources associated with the specified scopes. - /// - /// The scopes. - /// The that can be used to abort the operation. - /// All the resources associated with the specified scopes. - IAsyncEnumerable ListResourcesAsync(ImmutableArray scopes, CancellationToken cancellationToken = default); - - /// - /// Populates the specified descriptor using the properties exposed by the scope. - /// - /// The descriptor. - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PopulateAsync(OpenIddictScopeDescriptor descriptor, object scope, CancellationToken cancellationToken = default); - - /// - /// Populates the scope using the specified descriptor. - /// - /// The scope. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PopulateAsync(object scope, OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Updates an existing scope. - /// - /// The scope to update. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask UpdateAsync(object scope, CancellationToken cancellationToken = default); - - /// - /// Updates an existing scope. - /// - /// The scope to update. - /// The descriptor used to update the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask UpdateAsync(object scope, OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Validates the scope to ensure it's in a consistent state. - /// - /// The scope. - /// The that can be used to abort the operation. - /// The validation error encountered when validating the scope. - IAsyncEnumerable ValidateAsync(object scope, CancellationToken cancellationToken = default); - } + /// Determines the number of scopes that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of scopes in the database. + /// + ValueTask CountAsync(CancellationToken cancellationToken = default); + + /// + /// Determines the number of scopes that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of scopes that match the specified query. + /// + ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Creates a new scope based on the specified descriptor. + /// + /// The scope descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result returns the scope. + /// + ValueTask CreateAsync(OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Creates a new scope. + /// + /// The scope to create. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask CreateAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Removes an existing scope. + /// + /// The scope to delete. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask DeleteAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves a scope using its unique identifier. + /// + /// The unique identifier associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default); + + /// + /// Retrieves a scope using its name. + /// + /// The name associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the specified name. + /// + ValueTask FindByNameAsync(string name, CancellationToken cancellationToken = default); + + /// + /// Retrieves a list of scopes using their name. + /// + /// The names associated with the scopes. + /// The that can be used to abort the operation. + /// The scopes corresponding to the specified names. + IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken = default); + + /// + /// Retrieves all the scopes that contain the specified resource. + /// + /// The resource associated with the scopes. + /// The that can be used to abort the operation. + /// The scopes associated with the specified resource. + IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns the first element. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default); + + /// + /// Retrieves the description associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the description associated with the specified scope. + /// + ValueTask GetDescriptionAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves the localized descriptions associated with an scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the localized descriptions associated with the scope. + /// + ValueTask> GetDescriptionsAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves the display name associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the scope. + /// + ValueTask GetDisplayNameAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves the localized display names associated with an scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the localized display names associated with the scope. + /// + ValueTask> GetDisplayNamesAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves the unique identifier associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the scope. + /// + ValueTask GetIdAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves the localized description associated with an scope + /// and corresponding to the current UI culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching localized description associated with the scope. + /// + ValueTask GetLocalizedDescriptionAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves the localized description associated with an scope + /// and corresponding to the specified culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The scope. + /// The culture (typically ). + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching localized description associated with the scope. + /// + ValueTask GetLocalizedDescriptionAsync(object scope, CultureInfo culture, CancellationToken cancellationToken = default); + + /// + /// Retrieves the localized display name associated with an scope + /// and corresponding to the current UI culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the scope. + /// + ValueTask GetLocalizedDisplayNameAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves the localized display name associated with an scope + /// and corresponding to the specified culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The scope. + /// The culture (typically ). + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the scope. + /// + ValueTask GetLocalizedDisplayNameAsync(object scope, CultureInfo culture, CancellationToken cancellationToken = default); + + /// + /// Retrieves the name associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the name associated with the specified scope. + /// + ValueTask GetNameAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves the additional properties associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the scope. + /// + ValueTask> GetPropertiesAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Retrieves the resources associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the resources associated with the scope. + /// + ValueTask> GetResourcesAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + int? count = null, int? offset = null, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default); + + /// + /// Lists all the resources associated with the specified scopes. + /// + /// The scopes. + /// The that can be used to abort the operation. + /// All the resources associated with the specified scopes. + IAsyncEnumerable ListResourcesAsync(ImmutableArray scopes, CancellationToken cancellationToken = default); + + /// + /// Populates the specified descriptor using the properties exposed by the scope. + /// + /// The descriptor. + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PopulateAsync(OpenIddictScopeDescriptor descriptor, object scope, CancellationToken cancellationToken = default); + + /// + /// Populates the scope using the specified descriptor. + /// + /// The scope. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PopulateAsync(object scope, OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Updates an existing scope. + /// + /// The scope to update. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask UpdateAsync(object scope, CancellationToken cancellationToken = default); + + /// + /// Updates an existing scope. + /// + /// The scope to update. + /// The descriptor used to update the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask UpdateAsync(object scope, OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Validates the scope to ensure it's in a consistent state. + /// + /// The scope. + /// The that can be used to abort the operation. + /// The validation error encountered when validating the scope. + IAsyncEnumerable ValidateAsync(object scope, CancellationToken cancellationToken = default); } diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs index df816eae..4f7d82fa 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs @@ -13,460 +13,459 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to manage the tokens stored in the store. +/// Note: this interface is not meant to be implemented by custom managers, +/// that should inherit from the generic OpenIddictTokenManager class. +/// It is primarily intended to be used by services that cannot easily +/// depend on the generic token manager. The actual token entity type is +/// automatically determined at runtime based on the OpenIddict core options. +/// +public interface IOpenIddictTokenManager { /// - /// Provides methods allowing to manage the tokens stored in the store. - /// Note: this interface is not meant to be implemented by custom managers, - /// that should inherit from the generic OpenIddictTokenManager class. - /// It is primarily intended to be used by services that cannot easily - /// depend on the generic token manager. The actual token entity type is - /// automatically determined at runtime based on the OpenIddict core options. - /// - public interface IOpenIddictTokenManager - { - /// - /// Determines the number of tokens that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of tokens in the database. - /// - ValueTask CountAsync(CancellationToken cancellationToken = default); - - /// - /// Determines the number of tokens that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of tokens that match the specified query. - /// - ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Creates a new token based on the specified descriptor. - /// - /// The token descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the token. - /// - ValueTask CreateAsync(OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Creates a new token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask CreateAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Removes an existing token. - /// - /// The token to delete. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask DeleteAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The tokens corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, - string client, CancellationToken cancellationToken = default); - - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken = default); - - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken = default); - - /// - /// Retrieves the list of tokens corresponding to the specified application identifier. - /// - /// The application identifier associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified application. - IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default); - - /// - /// Retrieves the list of tokens corresponding to the specified authorization identifier. - /// - /// The authorization identifier associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified authorization. - IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken = default); - - /// - /// Retrieves a token using its unique identifier. - /// - /// The unique identifier associated with the token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the token corresponding to the unique identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default); - - /// - /// Retrieves the list of tokens corresponding to the specified reference identifier. - /// Note: the reference identifier may be hashed or encrypted for security reasons. - /// - /// The reference identifier associated with the tokens. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the tokens corresponding to the specified reference identifier. - /// - ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken = default); - - /// - /// Retrieves the list of tokens corresponding to the specified subject. - /// - /// The subject associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified subject. - IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken = default); - - /// - /// Retrieves the optional application identifier associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the application identifier associated with the token. - /// - ValueTask GetApplicationIdAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns the first element. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default); - - /// - /// Retrieves the optional authorization identifier associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization identifier associated with the token. - /// - ValueTask GetAuthorizationIdAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the creation date associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the creation date associated with the specified token. - /// - ValueTask GetCreationDateAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the expiration date associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the expiration date associated with the specified token. - /// - ValueTask GetExpirationDateAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the unique identifier associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the token. - /// - ValueTask GetIdAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the payload associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the payload associated with the specified token. - /// - ValueTask GetPayloadAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the additional properties associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the token. - /// - ValueTask> GetPropertiesAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the redemption date associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the redemption date associated with the specified token. - /// - ValueTask GetRedemptionDateAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the reference identifier associated with a token. - /// Note: depending on the manager used to create the token, - /// the reference identifier may be hashed for security reasons. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the reference identifier associated with the specified token. - /// - ValueTask GetReferenceIdAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the status associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the status associated with the specified token. - /// - ValueTask GetStatusAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the subject associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the subject associated with the specified token. - /// - ValueTask GetSubjectAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Retrieves the token type associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the token type associated with the specified token. - /// - ValueTask GetTypeAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Determines whether a given token has the specified status. - /// - /// The token. - /// The expected status. - /// The that can be used to abort the operation. - /// true if the token has the specified status, false otherwise. - ValueTask HasStatusAsync(object token, string status, CancellationToken cancellationToken = default); - - /// - /// Determines whether a given token has the specified type. - /// - /// The token. - /// The expected type. - /// The that can be used to abort the operation. - /// true if the token has the specified type, false otherwise. - ValueTask HasTypeAsync(object token, string type, CancellationToken cancellationToken = default); - - /// - /// Determines whether a given token has any of the specified types. - /// - /// The token. - /// The expected types. - /// The that can be used to abort the operation. - /// true if the token has any of the specified types, false otherwise. - ValueTask HasTypeAsync(object token, ImmutableArray types, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - int? count = null, int? offset = null, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default); - - /// - /// Populates the specified descriptor using the properties exposed by the token. - /// - /// The descriptor. - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PopulateAsync(OpenIddictTokenDescriptor descriptor, object token, CancellationToken cancellationToken = default); - - /// - /// Populates the token using the specified descriptor. - /// - /// The token. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PopulateAsync(object token, OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Removes the tokens that are marked as invalid or whose attached authorization is no longer valid. - /// Only tokens created before the specified are removed. - /// - /// The date before which tokens are not pruned. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default); - - /// - /// Tries to redeem a token. - /// - /// The token to redeem. - /// The that can be used to abort the operation. - /// true if the token was successfully redemeed, false otherwise. - ValueTask TryRedeemAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Tries to reject a token. - /// - /// The token to reject. - /// The that can be used to abort the operation. - /// true if the token was successfully redemeed, false otherwise. - ValueTask TryRejectAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Tries to revoke a token. - /// - /// The token to revoke. - /// The that can be used to abort the operation. - /// true if the token was successfully revoked, false otherwise. - ValueTask TryRevokeAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Updates an existing token. - /// - /// The token to update. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask UpdateAsync(object token, CancellationToken cancellationToken = default); - - /// - /// Updates an existing token. - /// - /// The token to update. - /// The descriptor used to update the token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask UpdateAsync(object token, OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default); - - /// - /// Validates the token to ensure it's in a consistent state. - /// - /// The token. - /// The that can be used to abort the operation. - /// The validation error encountered when validating the token. - IAsyncEnumerable ValidateAsync(object token, CancellationToken cancellationToken = default); - } + /// Determines the number of tokens that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of tokens in the database. + /// + ValueTask CountAsync(CancellationToken cancellationToken = default); + + /// + /// Determines the number of tokens that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of tokens that match the specified query. + /// + ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Creates a new token based on the specified descriptor. + /// + /// The token descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result returns the token. + /// + ValueTask CreateAsync(OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Creates a new token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask CreateAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Removes an existing token. + /// + /// The token to delete. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask DeleteAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the tokens corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The that can be used to abort the operation. + /// The tokens corresponding to the subject/client. + IAsyncEnumerable FindAsync(string subject, + string client, CancellationToken cancellationToken = default); + + /// + /// Retrieves the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The that can be used to abort the operation. + /// The tokens corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken = default); + + /// + /// Retrieves the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The token type. + /// The that can be used to abort the operation. + /// The tokens corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken = default); + + /// + /// Retrieves the list of tokens corresponding to the specified application identifier. + /// + /// The application identifier associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified application. + IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default); + + /// + /// Retrieves the list of tokens corresponding to the specified authorization identifier. + /// + /// The authorization identifier associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified authorization. + IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken = default); + + /// + /// Retrieves a token using its unique identifier. + /// + /// The unique identifier associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token corresponding to the unique identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default); + + /// + /// Retrieves the list of tokens corresponding to the specified reference identifier. + /// Note: the reference identifier may be hashed or encrypted for security reasons. + /// + /// The reference identifier associated with the tokens. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens corresponding to the specified reference identifier. + /// + ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken = default); + + /// + /// Retrieves the list of tokens corresponding to the specified subject. + /// + /// The subject associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified subject. + IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken = default); + + /// + /// Retrieves the optional application identifier associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the application identifier associated with the token. + /// + ValueTask GetApplicationIdAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns the first element. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default); + + /// + /// Retrieves the optional authorization identifier associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorization identifier associated with the token. + /// + ValueTask GetAuthorizationIdAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the creation date associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the creation date associated with the specified token. + /// + ValueTask GetCreationDateAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the expiration date associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the expiration date associated with the specified token. + /// + ValueTask GetExpirationDateAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the unique identifier associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + ValueTask GetIdAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the payload associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the payload associated with the specified token. + /// + ValueTask GetPayloadAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the additional properties associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the token. + /// + ValueTask> GetPropertiesAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the redemption date associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the redemption date associated with the specified token. + /// + ValueTask GetRedemptionDateAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the reference identifier associated with a token. + /// Note: depending on the manager used to create the token, + /// the reference identifier may be hashed for security reasons. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the reference identifier associated with the specified token. + /// + ValueTask GetReferenceIdAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the status associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the status associated with the specified token. + /// + ValueTask GetStatusAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the subject associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the subject associated with the specified token. + /// + ValueTask GetSubjectAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Retrieves the token type associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token type associated with the specified token. + /// + ValueTask GetTypeAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Determines whether a given token has the specified status. + /// + /// The token. + /// The expected status. + /// The that can be used to abort the operation. + /// true if the token has the specified status, false otherwise. + ValueTask HasStatusAsync(object token, string status, CancellationToken cancellationToken = default); + + /// + /// Determines whether a given token has the specified type. + /// + /// The token. + /// The expected type. + /// The that can be used to abort the operation. + /// true if the token has the specified type, false otherwise. + ValueTask HasTypeAsync(object token, string type, CancellationToken cancellationToken = default); + + /// + /// Determines whether a given token has any of the specified types. + /// + /// The token. + /// The expected types. + /// The that can be used to abort the operation. + /// true if the token has any of the specified types, false otherwise. + ValueTask HasTypeAsync(object token, ImmutableArray types, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + int? count = null, int? offset = null, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default); + + /// + /// Populates the specified descriptor using the properties exposed by the token. + /// + /// The descriptor. + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PopulateAsync(OpenIddictTokenDescriptor descriptor, object token, CancellationToken cancellationToken = default); + + /// + /// Populates the token using the specified descriptor. + /// + /// The token. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PopulateAsync(object token, OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Removes the tokens that are marked as invalid or whose attached authorization is no longer valid. + /// Only tokens created before the specified are removed. + /// + /// The date before which tokens are not pruned. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default); + + /// + /// Tries to redeem a token. + /// + /// The token to redeem. + /// The that can be used to abort the operation. + /// true if the token was successfully redemeed, false otherwise. + ValueTask TryRedeemAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Tries to reject a token. + /// + /// The token to reject. + /// The that can be used to abort the operation. + /// true if the token was successfully redemeed, false otherwise. + ValueTask TryRejectAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Tries to revoke a token. + /// + /// The token to revoke. + /// The that can be used to abort the operation. + /// true if the token was successfully revoked, false otherwise. + ValueTask TryRevokeAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Updates an existing token. + /// + /// The token to update. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask UpdateAsync(object token, CancellationToken cancellationToken = default); + + /// + /// Updates an existing token. + /// + /// The token to update. + /// The descriptor used to update the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask UpdateAsync(object token, OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default); + + /// + /// Validates the token to ensure it's in a consistent state. + /// + /// The token. + /// The that can be used to abort the operation. + /// The validation error encountered when validating the token. + IAsyncEnumerable ValidateAsync(object token, CancellationToken cancellationToken = default); } diff --git a/src/OpenIddict.Abstractions/OpenIddictBuilder.cs b/src/OpenIddict.Abstractions/OpenIddictBuilder.cs index a7c0d7c4..f19a615c 100644 --- a/src/OpenIddict.Abstractions/OpenIddictBuilder.cs +++ b/src/OpenIddict.Abstractions/OpenIddictBuilder.cs @@ -7,36 +7,35 @@ using System; using System.ComponentModel; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Provides a shared entry point allowing to configure the OpenIddict services. +/// +public class OpenIddictBuilder { /// - /// Provides a shared entry point allowing to configure the OpenIddict services. + /// Initializes a new instance of . /// - public class OpenIddictBuilder - { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); + /// The services collection. + public OpenIddictBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); - } + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Abstractions/OpenIddictConstants.cs b/src/OpenIddict.Abstractions/OpenIddictConstants.cs index 2f37ac84..4b59d442 100644 --- a/src/OpenIddict.Abstractions/OpenIddictConstants.cs +++ b/src/OpenIddict.Abstractions/OpenIddictConstants.cs @@ -4,477 +4,476 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +public static class OpenIddictConstants { - public static class OpenIddictConstants + public static class Algorithms { - public static class Algorithms - { - public const string EcdsaSha256 = "ES256"; - public const string EcdsaSha384 = "ES384"; - public const string EcdsaSha512 = "ES512"; - public const string HmacSha256 = "HS256"; - public const string HmacSha384 = "HS384"; - public const string HmacSha512 = "HS512"; - public const string RsaSha256 = "RS256"; - public const string RsaSha384 = "RS384"; - public const string RsaSha512 = "RS512"; - public const string RsaSsaPssSha256 = "PS256"; - public const string RsaSsaPssSha384 = "PS384"; - public const string RsaSsaPssSha512 = "PS512"; - } + public const string EcdsaSha256 = "ES256"; + public const string EcdsaSha384 = "ES384"; + public const string EcdsaSha512 = "ES512"; + public const string HmacSha256 = "HS256"; + public const string HmacSha384 = "HS384"; + public const string HmacSha512 = "HS512"; + public const string RsaSha256 = "RS256"; + public const string RsaSha384 = "RS384"; + public const string RsaSha512 = "RS512"; + public const string RsaSsaPssSha256 = "PS256"; + public const string RsaSsaPssSha384 = "PS384"; + public const string RsaSsaPssSha512 = "PS512"; + } - public static class AuthenticationMethodReferences - { - public const string Face = "face"; - public const string Fingerprint = "fpt"; - public const string Geolocation = "geo"; - public const string HardwareKey = "hwk"; - public const string Iris = "iris"; - public const string KnowledgeBased = "kba"; - public const string MultiChannel = "mca"; - public const string MultiFactor = "mfa"; - public const string OneTimePassword = "otp"; - public const string Password = "pwd"; - public const string PersonalIdentificationNumber = "pin"; - public const string Retina = "retina"; - public const string RiskBased = "rba"; - public const string ShortMessageService = "sms"; - public const string SmartCard = "sc"; - public const string SoftwareKey = "swk"; - public const string Telephone = "tel"; - public const string UserPresence = "user"; - public const string Voiceprint = "vbm"; - public const string WindowsIntegratedAuthentication = "wia"; - } + public static class AuthenticationMethodReferences + { + public const string Face = "face"; + public const string Fingerprint = "fpt"; + public const string Geolocation = "geo"; + public const string HardwareKey = "hwk"; + public const string Iris = "iris"; + public const string KnowledgeBased = "kba"; + public const string MultiChannel = "mca"; + public const string MultiFactor = "mfa"; + public const string OneTimePassword = "otp"; + public const string Password = "pwd"; + public const string PersonalIdentificationNumber = "pin"; + public const string Retina = "retina"; + public const string RiskBased = "rba"; + public const string ShortMessageService = "sms"; + public const string SmartCard = "sc"; + public const string SoftwareKey = "swk"; + public const string Telephone = "tel"; + public const string UserPresence = "user"; + public const string Voiceprint = "vbm"; + public const string WindowsIntegratedAuthentication = "wia"; + } - public static class AuthorizationTypes - { - public const string AdHoc = "ad-hoc"; - public const string Permanent = "permanent"; - } + public static class AuthorizationTypes + { + public const string AdHoc = "ad-hoc"; + public const string Permanent = "permanent"; + } - public static class Claims + public static class Claims + { + public const string AccessTokenHash = "at_hash"; + public const string Active = "active"; + public const string Address = "address"; + public const string Audience = "aud"; + public const string AuthenticationContextReference = "acr"; + public const string AuthenticationMethodReference = "amr"; + public const string AuthenticationTime = "auth_time"; + public const string AuthorizedParty = "azp"; + public const string Birthdate = "birthdate"; + public const string ClientId = "client_id"; + public const string CodeHash = "c_hash"; + public const string Country = "country"; + public const string Email = "email"; + public const string EmailVerified = "email_verified"; + public const string ExpiresAt = "exp"; + public const string FamilyName = "family_name"; + public const string Formatted = "formatted"; + public const string Gender = "gender"; + public const string GivenName = "given_name"; + public const string IssuedAt = "iat"; + public const string Issuer = "iss"; + public const string Locale = "locale"; + public const string Locality = "locality"; + public const string JwtId = "jti"; + public const string KeyId = "kid"; + public const string MiddleName = "middle_name"; + public const string Name = "name"; + public const string Nickname = "nickname"; + public const string Nonce = "nonce"; + public const string NotBefore = "nbf"; + public const string PhoneNumber = "phone_number"; + public const string PhoneNumberVerified = "phone_number_verified"; + public const string Picture = "picture"; + public const string PostalCode = "postal_code"; + public const string PreferredUsername = "preferred_username"; + public const string Profile = "profile"; + public const string Region = "region"; + public const string Role = "role"; + public const string Scope = "scope"; + public const string StreetAddress = "street_address"; + public const string Subject = "sub"; + public const string TokenType = "token_type"; + public const string TokenUsage = "token_usage"; + public const string UpdatedAt = "updated_at"; + public const string Username = "username"; + public const string Website = "website"; + public const string Zoneinfo = "zoneinfo"; + + public static class Prefixes { - public const string AccessTokenHash = "at_hash"; - public const string Active = "active"; - public const string Address = "address"; - public const string Audience = "aud"; - public const string AuthenticationContextReference = "acr"; - public const string AuthenticationMethodReference = "amr"; - public const string AuthenticationTime = "auth_time"; - public const string AuthorizedParty = "azp"; - public const string Birthdate = "birthdate"; - public const string ClientId = "client_id"; - public const string CodeHash = "c_hash"; - public const string Country = "country"; - public const string Email = "email"; - public const string EmailVerified = "email_verified"; - public const string ExpiresAt = "exp"; - public const string FamilyName = "family_name"; - public const string Formatted = "formatted"; - public const string Gender = "gender"; - public const string GivenName = "given_name"; - public const string IssuedAt = "iat"; - public const string Issuer = "iss"; - public const string Locale = "locale"; - public const string Locality = "locality"; - public const string JwtId = "jti"; - public const string KeyId = "kid"; - public const string MiddleName = "middle_name"; - public const string Name = "name"; - public const string Nickname = "nickname"; - public const string Nonce = "nonce"; - public const string NotBefore = "nbf"; - public const string PhoneNumber = "phone_number"; - public const string PhoneNumberVerified = "phone_number_verified"; - public const string Picture = "picture"; - public const string PostalCode = "postal_code"; - public const string PreferredUsername = "preferred_username"; - public const string Profile = "profile"; - public const string Region = "region"; - public const string Role = "role"; - public const string Scope = "scope"; - public const string StreetAddress = "street_address"; - public const string Subject = "sub"; - public const string TokenType = "token_type"; - public const string TokenUsage = "token_usage"; - public const string UpdatedAt = "updated_at"; - public const string Username = "username"; - public const string Website = "website"; - public const string Zoneinfo = "zoneinfo"; - - public static class Prefixes - { - public const string Private = "oi_"; - } - - public static class Private - { - public const string AccessTokenLifetime = "oi_act_lft"; - public const string Audience = "oi_aud"; - public const string AuthorizationCodeLifetime = "oi_auc_lft"; - public const string AuthorizationId = "oi_au_id"; - public const string ClaimDestinationsMap = "oi_cl_dstn"; - public const string CodeChallenge = "oi_cd_chlg"; - public const string CodeChallengeMethod = "oi_cd_chlg_meth"; - public const string CreationDate = "oi_crt_dt"; - public const string DeviceCodeId = "oi_dvc_id"; - public const string DeviceCodeLifetime = "oi_dvc_lft"; - public const string ExpirationDate = "oi_exp_dt"; - public const string IdentityTokenLifetime = "oi_idt_lft"; - public const string Nonce = "oi_nce"; - public const string Presenter = "oi_prst"; - public const string RedirectUri = "oi_reduri"; - public const string RefreshTokenLifetime = "oi_reft_lft"; - public const string Resource = "oi_rsrc"; - public const string Scope = "oi_scp"; - public const string TokenId = "oi_tkn_id"; - public const string TokenType = "oi_tkn_typ"; - public const string UserCodeLifetime = "oi_usrc_lft"; - } + public const string Private = "oi_"; } - public static class ClientAuthenticationMethods + public static class Private { - public const string ClientSecretBasic = "client_secret_basic"; - public const string ClientSecretPost = "client_secret_post"; + public const string AccessTokenLifetime = "oi_act_lft"; + public const string Audience = "oi_aud"; + public const string AuthorizationCodeLifetime = "oi_auc_lft"; + public const string AuthorizationId = "oi_au_id"; + public const string ClaimDestinationsMap = "oi_cl_dstn"; + public const string CodeChallenge = "oi_cd_chlg"; + public const string CodeChallengeMethod = "oi_cd_chlg_meth"; + public const string CreationDate = "oi_crt_dt"; + public const string DeviceCodeId = "oi_dvc_id"; + public const string DeviceCodeLifetime = "oi_dvc_lft"; + public const string ExpirationDate = "oi_exp_dt"; + public const string IdentityTokenLifetime = "oi_idt_lft"; + public const string Nonce = "oi_nce"; + public const string Presenter = "oi_prst"; + public const string RedirectUri = "oi_reduri"; + public const string RefreshTokenLifetime = "oi_reft_lft"; + public const string Resource = "oi_rsrc"; + public const string Scope = "oi_scp"; + public const string TokenId = "oi_tkn_id"; + public const string TokenType = "oi_tkn_typ"; + public const string UserCodeLifetime = "oi_usrc_lft"; } + } - public static class ClientTypes - { - public const string Confidential = "confidential"; - public const string Public = "public"; - } + public static class ClientAuthenticationMethods + { + public const string ClientSecretBasic = "client_secret_basic"; + public const string ClientSecretPost = "client_secret_post"; + } - public static class CodeChallengeMethods - { - public const string Plain = "plain"; - public const string Sha256 = "S256"; - } + public static class ClientTypes + { + public const string Confidential = "confidential"; + public const string Public = "public"; + } - public static class ConsentTypes - { - public const string Explicit = "explicit"; - public const string External = "external"; - public const string Implicit = "implicit"; - public const string Systematic = "systematic"; - } + public static class CodeChallengeMethods + { + public const string Plain = "plain"; + public const string Sha256 = "S256"; + } - public static class Destinations - { - public const string AccessToken = "access_token"; - public const string IdentityToken = "id_token"; - } + public static class ConsentTypes + { + public const string Explicit = "explicit"; + public const string External = "external"; + public const string Implicit = "implicit"; + public const string Systematic = "systematic"; + } - public static class Errors - { - public const string AccessDenied = "access_denied"; - public const string AccountSelectionRequired = "account_selection_required"; - public const string AuthorizationPending = "authorization_pending"; - public const string ConsentRequired = "consent_required"; - public const string ExpiredToken = "expired_token"; - public const string InsufficientAccess = "insufficient_access"; - public const string InsufficientScope = "insufficient_scope"; - public const string InteractionRequired = "interaction_required"; - public const string InvalidClient = "invalid_client"; - public const string InvalidGrant = "invalid_grant"; - public const string InvalidRequest = "invalid_request"; - public const string InvalidRequestObject = "invalid_request_object"; - public const string InvalidRequestUri = "invalid_request_uri"; - public const string InvalidScope = "invalid_scope"; - public const string InvalidToken = "invalid_token"; - public const string LoginRequired = "login_required"; - public const string MissingToken = "missing_token"; - public const string RegistrationNotSupported = "registration_not_supported"; - public const string RequestNotSupported = "request_not_supported"; - public const string RequestUriNotSupported = "request_uri_not_supported"; - public const string ServerError = "server_error"; - public const string SlowDown = "slow_down"; - public const string TemporarilyUnavailable = "temporarily_unavailable"; - public const string UnauthorizedClient = "unauthorized_client"; - public const string UnsupportedGrantType = "unsupported_grant_type"; - public const string UnsupportedResponseType = "unsupported_response_type"; - public const string UnsupportedTokenType = "unsupported_token_type"; - } + public static class Destinations + { + public const string AccessToken = "access_token"; + public const string IdentityToken = "id_token"; + } - public static class GrantTypes - { - public const string AuthorizationCode = "authorization_code"; - public const string ClientCredentials = "client_credentials"; - public const string DeviceCode = "urn:ietf:params:oauth:grant-type:device_code"; - public const string Implicit = "implicit"; - public const string Password = "password"; - public const string RefreshToken = "refresh_token"; - } + public static class Errors + { + public const string AccessDenied = "access_denied"; + public const string AccountSelectionRequired = "account_selection_required"; + public const string AuthorizationPending = "authorization_pending"; + public const string ConsentRequired = "consent_required"; + public const string ExpiredToken = "expired_token"; + public const string InsufficientAccess = "insufficient_access"; + public const string InsufficientScope = "insufficient_scope"; + public const string InteractionRequired = "interaction_required"; + public const string InvalidClient = "invalid_client"; + public const string InvalidGrant = "invalid_grant"; + public const string InvalidRequest = "invalid_request"; + public const string InvalidRequestObject = "invalid_request_object"; + public const string InvalidRequestUri = "invalid_request_uri"; + public const string InvalidScope = "invalid_scope"; + public const string InvalidToken = "invalid_token"; + public const string LoginRequired = "login_required"; + public const string MissingToken = "missing_token"; + public const string RegistrationNotSupported = "registration_not_supported"; + public const string RequestNotSupported = "request_not_supported"; + public const string RequestUriNotSupported = "request_uri_not_supported"; + public const string ServerError = "server_error"; + public const string SlowDown = "slow_down"; + public const string TemporarilyUnavailable = "temporarily_unavailable"; + public const string UnauthorizedClient = "unauthorized_client"; + public const string UnsupportedGrantType = "unsupported_grant_type"; + public const string UnsupportedResponseType = "unsupported_response_type"; + public const string UnsupportedTokenType = "unsupported_token_type"; + } - public static class JsonWebTokenTypes - { - public const string AccessToken = "at+jwt"; - public const string IdentityToken = "JWT"; - - public static class Prefixes - { - public const string Application = "application/"; - } - - public static class Private - { - public const string AuthorizationCode = "oi_auc+jwt"; - public const string DeviceCode = "oi_dvc+jwt"; - public const string RefreshToken = "oi_reft+jwt"; - public const string UserCode = "oi_usrc+jwt"; - } - } + public static class GrantTypes + { + public const string AuthorizationCode = "authorization_code"; + public const string ClientCredentials = "client_credentials"; + public const string DeviceCode = "urn:ietf:params:oauth:grant-type:device_code"; + public const string Implicit = "implicit"; + public const string Password = "password"; + public const string RefreshToken = "refresh_token"; + } - public static class Metadata - { - public const string AcrValuesSupported = "acr_values_supported"; - public const string AuthorizationEndpoint = "authorization_endpoint"; - public const string ClaimsLocalesSupported = "claims_locales_supported"; - public const string ClaimsParameterSupported = "claims_parameter_supported"; - public const string ClaimsSupported = "claims_supported"; - public const string ClaimTypesSupported = "claim_types_supported"; - public const string CodeChallengeMethodsSupported = "code_challenge_methods_supported"; - public const string DeviceAuthorizationEndpoint = "device_authorization_endpoint"; - public const string DisplayValuesSupported = "display_values_supported"; - public const string EndSessionEndpoint = "end_session_endpoint"; - public const string GrantTypesSupported = "grant_types_supported"; - public const string IdTokenEncryptionAlgValuesSupported = "id_token_encryption_alg_values_supported"; - public const string IdTokenEncryptionEncValuesSupported = "id_token_encryption_enc_values_supported"; - public const string IdTokenSigningAlgValuesSupported = "id_token_signing_alg_values_supported"; - public const string IntrospectionEndpoint = "introspection_endpoint"; - public const string IntrospectionEndpointAuthMethodsSupported = "introspection_endpoint_auth_methods_supported"; - public const string IntrospectionEndpointAuthSigningAlgValuesSupported = "introspection_endpoint_auth_signing_alg_values_supported"; - public const string Issuer = "issuer"; - public const string JwksUri = "jwks_uri"; - public const string OpPolicyUri = "op_policy_uri"; - public const string OpTosUri = "op_tos_uri"; - public const string RequestObjectEncryptionAlgValuesSupported = "request_object_encryption_alg_values_supported"; - public const string RequestObjectEncryptionEncValuesSupported = "request_object_encryption_enc_values_supported"; - public const string RequestObjectSigningAlgValuesSupported = "request_object_signing_alg_values_supported"; - public const string RequestParameterSupported = "request_parameter_supported"; - public const string RequestUriParameterSupported = "request_uri_parameter_supported"; - public const string RequireRequestUriRegistration = "require_request_uri_registration"; - public const string ResponseModesSupported = "response_modes_supported"; - public const string ResponseTypesSupported = "response_types_supported"; - public const string RevocationEndpoint = "revocation_endpoint"; - public const string RevocationEndpointAuthMethodsSupported = "revocation_endpoint_auth_methods_supported"; - public const string RevocationEndpointAuthSigningAlgValuesSupported = "revocation_endpoint_auth_signing_alg_values_supported"; - public const string ScopesSupported = "scopes_supported"; - public const string ServiceDocumentation = "service_documentation"; - public const string SubjectTypesSupported = "subject_types_supported"; - public const string TokenEndpoint = "token_endpoint"; - public const string TokenEndpointAuthMethodsSupported = "token_endpoint_auth_methods_supported"; - public const string TokenEndpointAuthSigningAlgValuesSupported = "token_endpoint_auth_signing_alg_values_supported"; - public const string UiLocalesSupported = "ui_locales_supported"; - public const string UserinfoEncryptionAlgValuesSupported = "userinfo_encryption_alg_values_supported"; - public const string UserinfoEncryptionEncValuesSupported = "userinfo_encryption_enc_values_supported"; - public const string UserinfoEndpoint = "userinfo_endpoint"; - public const string UserinfoSigningAlgValuesSupported = "userinfo_signing_alg_values_supported"; - } + public static class JsonWebTokenTypes + { + public const string AccessToken = "at+jwt"; + public const string IdentityToken = "JWT"; - public static class Parameters + public static class Prefixes { - public const string AccessToken = "access_token"; - public const string Active = "active"; - public const string AcrValues = "acr_values"; - public const string Assertion = "assertion"; - public const string Audience = "audience"; - public const string Claims = "claims"; - public const string ClaimsLocales = "claims_locales"; - public const string ClientAssertion = "client_assertion"; - public const string ClientAssertionType = "client_assertion_type"; - public const string ClientId = "client_id"; - public const string ClientSecret = "client_secret"; - public const string Code = "code"; - public const string CodeChallenge = "code_challenge"; - public const string CodeChallengeMethod = "code_challenge_method"; - public const string CodeVerifier = "code_verifier"; - public const string DeviceCode = "device_code"; - public const string Display = "display"; - public const string Error = "error"; - public const string ErrorDescription = "error_description"; - public const string ErrorUri = "error_uri"; - public const string ExpiresIn = "expires_in"; - public const string GrantType = "grant_type"; - public const string IdentityProvider = "identity_provider"; - public const string IdToken = "id_token"; - public const string IdTokenHint = "id_token_hint"; - public const string LoginHint = "login_hint"; - public const string Keys = "keys"; - public const string MaxAge = "max_age"; - public const string Nonce = "nonce"; - public const string Password = "password"; - public const string PostLogoutRedirectUri = "post_logout_redirect_uri"; - public const string Prompt = "prompt"; - public const string Realm = "realm"; - public const string RedirectUri = "redirect_uri"; - public const string RefreshToken = "refresh_token"; - public const string Registration = "registration"; - public const string Request = "request"; - public const string RequestId = "request_id"; - public const string RequestUri = "request_uri"; - public const string Resource = "resource"; - public const string ResponseMode = "response_mode"; - public const string ResponseType = "response_type"; - public const string Scope = "scope"; - public const string State = "state"; - public const string Token = "token"; - public const string TokenType = "token_type"; - public const string TokenTypeHint = "token_type_hint"; - public const string UiLocales = "ui_locales"; - public const string UserCode = "user_code"; - public const string Username = "username"; - public const string VerificationUri = "verification_uri"; - public const string VerificationUriComplete = "verification_uri_complete"; + public const string Application = "application/"; } - public static class Permissions + public static class Private { - public static class Endpoints - { - public const string Authorization = "ept:authorization"; - public const string Device = "ept:device"; - public const string Introspection = "ept:introspection"; - public const string Logout = "ept:logout"; - public const string Revocation = "ept:revocation"; - public const string Token = "ept:token"; - } - - public static class GrantTypes - { - public const string AuthorizationCode = "gt:authorization_code"; - public const string ClientCredentials = "gt:client_credentials"; - public const string DeviceCode = "gt:urn:ietf:params:oauth:grant-type:device_code"; - public const string Implicit = "gt:implicit"; - public const string Password = "gt:password"; - public const string RefreshToken = "gt:refresh_token"; - } - - public static class Prefixes - { - public const string Endpoint = "ept:"; - public const string GrantType = "gt:"; - public const string ResponseType = "rst:"; - public const string Scope = "scp:"; - } - - public static class ResponseTypes - { - public const string Code = "rst:code"; - public const string CodeIdToken = "rst:code id_token"; - public const string CodeIdTokenToken = "rst:code id_token token"; - public const string CodeToken = "rst:code token"; - public const string IdToken = "rst:id_token"; - public const string IdTokenToken = "rst:id_token token"; - public const string None = "rst:none"; - public const string Token = "rst:token"; - } - - public static class Scopes - { - public const string Address = "scp:address"; - public const string Email = "scp:email"; - public const string Phone = "scp:phone"; - public const string Profile = "scp:profile"; - public const string Roles = "scp:roles"; - } + public const string AuthorizationCode = "oi_auc+jwt"; + public const string DeviceCode = "oi_dvc+jwt"; + public const string RefreshToken = "oi_reft+jwt"; + public const string UserCode = "oi_usrc+jwt"; } + } - public static class Prompts - { - public const string Consent = "consent"; - public const string Login = "login"; - public const string None = "none"; - public const string SelectAccount = "select_account"; - } + public static class Metadata + { + public const string AcrValuesSupported = "acr_values_supported"; + public const string AuthorizationEndpoint = "authorization_endpoint"; + public const string ClaimsLocalesSupported = "claims_locales_supported"; + public const string ClaimsParameterSupported = "claims_parameter_supported"; + public const string ClaimsSupported = "claims_supported"; + public const string ClaimTypesSupported = "claim_types_supported"; + public const string CodeChallengeMethodsSupported = "code_challenge_methods_supported"; + public const string DeviceAuthorizationEndpoint = "device_authorization_endpoint"; + public const string DisplayValuesSupported = "display_values_supported"; + public const string EndSessionEndpoint = "end_session_endpoint"; + public const string GrantTypesSupported = "grant_types_supported"; + public const string IdTokenEncryptionAlgValuesSupported = "id_token_encryption_alg_values_supported"; + public const string IdTokenEncryptionEncValuesSupported = "id_token_encryption_enc_values_supported"; + public const string IdTokenSigningAlgValuesSupported = "id_token_signing_alg_values_supported"; + public const string IntrospectionEndpoint = "introspection_endpoint"; + public const string IntrospectionEndpointAuthMethodsSupported = "introspection_endpoint_auth_methods_supported"; + public const string IntrospectionEndpointAuthSigningAlgValuesSupported = "introspection_endpoint_auth_signing_alg_values_supported"; + public const string Issuer = "issuer"; + public const string JwksUri = "jwks_uri"; + public const string OpPolicyUri = "op_policy_uri"; + public const string OpTosUri = "op_tos_uri"; + public const string RequestObjectEncryptionAlgValuesSupported = "request_object_encryption_alg_values_supported"; + public const string RequestObjectEncryptionEncValuesSupported = "request_object_encryption_enc_values_supported"; + public const string RequestObjectSigningAlgValuesSupported = "request_object_signing_alg_values_supported"; + public const string RequestParameterSupported = "request_parameter_supported"; + public const string RequestUriParameterSupported = "request_uri_parameter_supported"; + public const string RequireRequestUriRegistration = "require_request_uri_registration"; + public const string ResponseModesSupported = "response_modes_supported"; + public const string ResponseTypesSupported = "response_types_supported"; + public const string RevocationEndpoint = "revocation_endpoint"; + public const string RevocationEndpointAuthMethodsSupported = "revocation_endpoint_auth_methods_supported"; + public const string RevocationEndpointAuthSigningAlgValuesSupported = "revocation_endpoint_auth_signing_alg_values_supported"; + public const string ScopesSupported = "scopes_supported"; + public const string ServiceDocumentation = "service_documentation"; + public const string SubjectTypesSupported = "subject_types_supported"; + public const string TokenEndpoint = "token_endpoint"; + public const string TokenEndpointAuthMethodsSupported = "token_endpoint_auth_methods_supported"; + public const string TokenEndpointAuthSigningAlgValuesSupported = "token_endpoint_auth_signing_alg_values_supported"; + public const string UiLocalesSupported = "ui_locales_supported"; + public const string UserinfoEncryptionAlgValuesSupported = "userinfo_encryption_alg_values_supported"; + public const string UserinfoEncryptionEncValuesSupported = "userinfo_encryption_enc_values_supported"; + public const string UserinfoEndpoint = "userinfo_endpoint"; + public const string UserinfoSigningAlgValuesSupported = "userinfo_signing_alg_values_supported"; + } + + public static class Parameters + { + public const string AccessToken = "access_token"; + public const string Active = "active"; + public const string AcrValues = "acr_values"; + public const string Assertion = "assertion"; + public const string Audience = "audience"; + public const string Claims = "claims"; + public const string ClaimsLocales = "claims_locales"; + public const string ClientAssertion = "client_assertion"; + public const string ClientAssertionType = "client_assertion_type"; + public const string ClientId = "client_id"; + public const string ClientSecret = "client_secret"; + public const string Code = "code"; + public const string CodeChallenge = "code_challenge"; + public const string CodeChallengeMethod = "code_challenge_method"; + public const string CodeVerifier = "code_verifier"; + public const string DeviceCode = "device_code"; + public const string Display = "display"; + public const string Error = "error"; + public const string ErrorDescription = "error_description"; + public const string ErrorUri = "error_uri"; + public const string ExpiresIn = "expires_in"; + public const string GrantType = "grant_type"; + public const string IdentityProvider = "identity_provider"; + public const string IdToken = "id_token"; + public const string IdTokenHint = "id_token_hint"; + public const string LoginHint = "login_hint"; + public const string Keys = "keys"; + public const string MaxAge = "max_age"; + public const string Nonce = "nonce"; + public const string Password = "password"; + public const string PostLogoutRedirectUri = "post_logout_redirect_uri"; + public const string Prompt = "prompt"; + public const string Realm = "realm"; + public const string RedirectUri = "redirect_uri"; + public const string RefreshToken = "refresh_token"; + public const string Registration = "registration"; + public const string Request = "request"; + public const string RequestId = "request_id"; + public const string RequestUri = "request_uri"; + public const string Resource = "resource"; + public const string ResponseMode = "response_mode"; + public const string ResponseType = "response_type"; + public const string Scope = "scope"; + public const string State = "state"; + public const string Token = "token"; + public const string TokenType = "token_type"; + public const string TokenTypeHint = "token_type_hint"; + public const string UiLocales = "ui_locales"; + public const string UserCode = "user_code"; + public const string Username = "username"; + public const string VerificationUri = "verification_uri"; + public const string VerificationUriComplete = "verification_uri_complete"; + } - public static class Properties + public static class Permissions + { + public static class Endpoints { - public const string Destinations = ".destinations"; + public const string Authorization = "ept:authorization"; + public const string Device = "ept:device"; + public const string Introspection = "ept:introspection"; + public const string Logout = "ept:logout"; + public const string Revocation = "ept:revocation"; + public const string Token = "ept:token"; } - public static class Requirements + public static class GrantTypes { - public static class Features - { - public const string ProofKeyForCodeExchange = "ft:pkce"; - } - - public static class Prefixes - { - public const string Feature = "ft:"; - } + public const string AuthorizationCode = "gt:authorization_code"; + public const string ClientCredentials = "gt:client_credentials"; + public const string DeviceCode = "gt:urn:ietf:params:oauth:grant-type:device_code"; + public const string Implicit = "gt:implicit"; + public const string Password = "gt:password"; + public const string RefreshToken = "gt:refresh_token"; } - public static class ResponseModes + public static class Prefixes { - public const string FormPost = "form_post"; - public const string Fragment = "fragment"; - public const string Query = "query"; + public const string Endpoint = "ept:"; + public const string GrantType = "gt:"; + public const string ResponseType = "rst:"; + public const string Scope = "scp:"; } public static class ResponseTypes { - public const string Code = "code"; - public const string IdToken = "id_token"; - public const string None = "none"; - public const string Token = "token"; + public const string Code = "rst:code"; + public const string CodeIdToken = "rst:code id_token"; + public const string CodeIdTokenToken = "rst:code id_token token"; + public const string CodeToken = "rst:code token"; + public const string IdToken = "rst:id_token"; + public const string IdTokenToken = "rst:id_token token"; + public const string None = "rst:none"; + public const string Token = "rst:token"; } - public static class Separators + public static class Scopes { - public static readonly char[] Ampersand = { '&' }; - public static readonly char[] Dash = { '-' }; - public static readonly char[] Space = { ' ' }; + public const string Address = "scp:address"; + public const string Email = "scp:email"; + public const string Phone = "scp:phone"; + public const string Profile = "scp:profile"; + public const string Roles = "scp:roles"; } + } - public static class Schemes - { - public const string Basic = "Basic"; - public const string Bearer = "Bearer"; - } + public static class Prompts + { + public const string Consent = "consent"; + public const string Login = "login"; + public const string None = "none"; + public const string SelectAccount = "select_account"; + } - public static class Scopes - { - public const string Address = "address"; - public const string Email = "email"; - public const string OfflineAccess = "offline_access"; - public const string OpenId = "openid"; - public const string Phone = "phone"; - public const string Profile = "profile"; - public const string Roles = "roles"; - } + public static class Properties + { + public const string Destinations = ".destinations"; + } - public static class Statuses + public static class Requirements + { + public static class Features { - public const string Inactive = "inactive"; - public const string Redeemed = "redeemed"; - public const string Rejected = "rejected"; - public const string Revoked = "revoked"; - public const string Valid = "valid"; + public const string ProofKeyForCodeExchange = "ft:pkce"; } - public static class SubjectTypes + public static class Prefixes { - public const string Pairwise = "pairwise"; - public const string Public = "public"; + public const string Feature = "ft:"; } + } - public static class TokenTypeHints - { - public const string AccessToken = "access_token"; - public const string AuthorizationCode = "authorization_code"; - public const string DeviceCode = "device_code"; - public const string IdToken = "id_token"; - public const string RefreshToken = "refresh_token"; - public const string UserCode = "user_code"; - } + public static class ResponseModes + { + public const string FormPost = "form_post"; + public const string Fragment = "fragment"; + public const string Query = "query"; + } - public static class TokenTypes - { - public const string Bearer = "Bearer"; - } + public static class ResponseTypes + { + public const string Code = "code"; + public const string IdToken = "id_token"; + public const string None = "none"; + public const string Token = "token"; + } + + public static class Separators + { + public static readonly char[] Ampersand = { '&' }; + public static readonly char[] Dash = { '-' }; + public static readonly char[] Space = { ' ' }; + } + + public static class Schemes + { + public const string Basic = "Basic"; + public const string Bearer = "Bearer"; + } + + public static class Scopes + { + public const string Address = "address"; + public const string Email = "email"; + public const string OfflineAccess = "offline_access"; + public const string OpenId = "openid"; + public const string Phone = "phone"; + public const string Profile = "profile"; + public const string Roles = "roles"; + } + + public static class Statuses + { + public const string Inactive = "inactive"; + public const string Redeemed = "redeemed"; + public const string Rejected = "rejected"; + public const string Revoked = "revoked"; + public const string Valid = "valid"; + } + + public static class SubjectTypes + { + public const string Pairwise = "pairwise"; + public const string Public = "public"; + } + + public static class TokenTypeHints + { + public const string AccessToken = "access_token"; + public const string AuthorizationCode = "authorization_code"; + public const string DeviceCode = "device_code"; + public const string IdToken = "id_token"; + public const string RefreshToken = "refresh_token"; + public const string UserCode = "user_code"; + } + + public static class TokenTypes + { + public const string Bearer = "Bearer"; } } diff --git a/src/OpenIddict.Abstractions/OpenIddictExceptions.cs b/src/OpenIddict.Abstractions/OpenIddictExceptions.cs index 0ffd23a3..8de4553f 100644 --- a/src/OpenIddict.Abstractions/OpenIddictExceptions.cs +++ b/src/OpenIddict.Abstractions/OpenIddictExceptions.cs @@ -2,144 +2,143 @@ using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Exposes common exceptions thrown by OpenIddict. +/// +public static class OpenIddictExceptions { /// - /// Exposes common exceptions thrown by OpenIddict. + /// Represents an OpenIddict concurrency exception. /// - public static class OpenIddictExceptions + public class ConcurrencyException : Exception { /// - /// Represents an OpenIddict concurrency exception. + /// Creates a new . /// - public class ConcurrencyException : Exception + /// The exception message. + public ConcurrencyException(string? message) + : this(message, exception: null) { - /// - /// Creates a new . - /// - /// The exception message. - public ConcurrencyException(string? message) - : this(message, exception: null) - { - } + } - /// - /// Creates a new . - /// - /// The exception message. - /// The inner exception. - public ConcurrencyException(string? message, Exception? exception) - : base(message, exception) - { - } + /// + /// Creates a new . + /// + /// The exception message. + /// The inner exception. + public ConcurrencyException(string? message, Exception? exception) + : base(message, exception) + { } + } + /// + /// Represents a generic OpenIddict exception. + /// + public class GenericException : Exception + { /// - /// Represents a generic OpenIddict exception. + /// Creates a new . /// - public class GenericException : Exception + /// The exception message. + public GenericException(string? message) + : this(message, null) { - /// - /// Creates a new . - /// - /// The exception message. - public GenericException(string? message) - : this(message, null) - { - } + } - /// - /// Creates a new . - /// - /// The exception message. - /// The error type. - public GenericException(string? message, string? error) - : this(message, error, description: null) - { - } + /// + /// Creates a new . + /// + /// The exception message. + /// The error type. + public GenericException(string? message, string? error) + : this(message, error, description: null) + { + } - /// - /// Creates a new . - /// - /// The exception message. - /// The error type. - /// The error description. - public GenericException(string? message, string? error, string? description) - : this(message, error, description, uri: null) - { - } + /// + /// Creates a new . + /// + /// The exception message. + /// The error type. + /// The error description. + public GenericException(string? message, string? error, string? description) + : this(message, error, description, uri: null) + { + } - /// - /// Creates a new . - /// - /// The exception message. - /// The error type. - /// The error description. - /// The error URI. - public GenericException(string? message, string? error, string? description, string? uri) - : base(message) - { - Error = error; - ErrorDescription = description; - ErrorUri = uri; - } + /// + /// Creates a new . + /// + /// The exception message. + /// The error type. + /// The error description. + /// The error URI. + public GenericException(string? message, string? error, string? description, string? uri) + : base(message) + { + Error = error; + ErrorDescription = description; + ErrorUri = uri; + } - /// - /// Gets the error type. - /// - public string? Error { get; } + /// + /// Gets the error type. + /// + public string? Error { get; } - /// - /// Gets the error description. - /// - public string? ErrorDescription { get; } + /// + /// Gets the error description. + /// + public string? ErrorDescription { get; } - /// - /// Gets the error URI. - /// - public string? ErrorUri { get; } - } + /// + /// Gets the error URI. + /// + public string? ErrorUri { get; } + } + /// + /// Represents an OpenIddict validation exception. + /// + public class ValidationException : Exception + { /// - /// Represents an OpenIddict validation exception. + /// Creates a new . /// - public class ValidationException : Exception + /// The exception message. + public ValidationException(string? message) + : this(message, ImmutableArray.Create()) { - /// - /// Creates a new . - /// - /// The exception message. - public ValidationException(string? message) - : this(message, ImmutableArray.Create()) - { - } - - /// - /// Creates a new . - /// - /// The exception message. - /// The validation results. - public ValidationException(string? message, ImmutableArray results) - : this(message, results, exception: null) - { - } + } - /// - /// Creates a new . - /// - /// The exception message. - /// The validation results. - /// The inner exception. - public ValidationException(string? message, ImmutableArray results, Exception? exception) - : base(message, exception) - { - Results = results; - } + /// + /// Creates a new . + /// + /// The exception message. + /// The validation results. + public ValidationException(string? message, ImmutableArray results) + : this(message, results, exception: null) + { + } - /// - /// Gets the validation results associated with this exception. - /// - public ImmutableArray Results { get; } + /// + /// Creates a new . + /// + /// The exception message. + /// The validation results. + /// The inner exception. + public ValidationException(string? message, ImmutableArray results, Exception? exception) + : base(message, exception) + { + Results = results; } + + /// + /// Gets the validation results associated with this exception. + /// + public ImmutableArray Results { get; } } } diff --git a/src/OpenIddict.Abstractions/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/OpenIddictExtensions.cs index bad57860..00e343c4 100644 --- a/src/OpenIddict.Abstractions/OpenIddictExtensions.cs +++ b/src/OpenIddict.Abstractions/OpenIddictExtensions.cs @@ -6,51 +6,50 @@ using System; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict services. +/// +public static class OpenIddictExtensions { /// - /// Exposes extensions allowing to register the OpenIddict services. + /// Provides a common entry point for registering the OpenIddict services. /// - public static class OpenIddictExtensions + /// The services collection. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictBuilder AddOpenIddict(this IServiceCollection services) { - /// - /// Provides a common entry point for registering the OpenIddict services. - /// - /// The services collection. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictBuilder AddOpenIddict(this IServiceCollection services) + if (services is null) { - if (services is null) - { - throw new ArgumentNullException(nameof(services)); - } - - return new OpenIddictBuilder(services); + throw new ArgumentNullException(nameof(services)); } - /// - /// Provides a common entry point for registering the OpenIddict services. - /// - /// The services collection. - /// The configuration delegate used to register new services. - /// This extension can be safely called multiple times. - /// The . - public static IServiceCollection AddOpenIddict(this IServiceCollection services, Action configuration) + return new OpenIddictBuilder(services); + } + + /// + /// Provides a common entry point for registering the OpenIddict services. + /// + /// The services collection. + /// The configuration delegate used to register new services. + /// This extension can be safely called multiple times. + /// The . + public static IServiceCollection AddOpenIddict(this IServiceCollection services, Action configuration) + { + if (services is null) { - if (services is null) - { - throw new ArgumentNullException(nameof(services)); - } + throw new ArgumentNullException(nameof(services)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(services.AddOpenIddict()); + configuration(services.AddOpenIddict()); - return services; - } + return services; } -} \ No newline at end of file +} diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs index a6c63f02..eee01846 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs @@ -9,71 +9,70 @@ using System.Text.Json; using System.Text.Json.Serialization; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Represents a JSON.NET converter able to convert OpenIddict primitives. +/// +public class OpenIddictConverter : JsonConverter { /// - /// Represents a JSON.NET converter able to convert OpenIddict primitives. + /// Determines whether the specified type is supported by this converter. /// - public class OpenIddictConverter : JsonConverter + /// The type to convert. + /// true if the type is supported, false otherwise. + public override bool CanConvert(Type typeToConvert) { - /// - /// Determines whether the specified type is supported by this converter. - /// - /// The type to convert. - /// true if the type is supported, false otherwise. - public override bool CanConvert(Type typeToConvert) + if (typeToConvert is null) { - if (typeToConvert is null) - { - throw new ArgumentNullException(nameof(typeToConvert)); - } - - return typeToConvert == typeof(OpenIddictMessage) || - typeToConvert == typeof(OpenIddictRequest) || - typeToConvert == typeof(OpenIddictResponse); + throw new ArgumentNullException(nameof(typeToConvert)); } - /// - /// Deserializes an instance. - /// - /// The JSON reader. - /// The type of the deserialized instance. - /// The JSON serializer options. - /// The deserialized instance. - public override OpenIddictMessage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + return typeToConvert == typeof(OpenIddictMessage) || + typeToConvert == typeof(OpenIddictRequest) || + typeToConvert == typeof(OpenIddictResponse); + } + + /// + /// Deserializes an instance. + /// + /// The JSON reader. + /// The type of the deserialized instance. + /// The JSON serializer options. + /// The deserialized instance. + public override OpenIddictMessage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (typeToConvert is null) { - if (typeToConvert is null) - { - throw new ArgumentNullException(nameof(typeToConvert)); - } + throw new ArgumentNullException(nameof(typeToConvert)); + } - using var document = JsonDocument.ParseValue(ref reader); + using var document = JsonDocument.ParseValue(ref reader); - return typeToConvert == typeof(OpenIddictMessage) ? new OpenIddictMessage(document.RootElement.Clone()) : - typeToConvert == typeof(OpenIddictRequest) ? new OpenIddictRequest(document.RootElement.Clone()) : - typeToConvert == typeof(OpenIddictResponse) ? new OpenIddictResponse(document.RootElement.Clone()) : - throw new ArgumentException(SR.GetResourceString(SR.ID0176), nameof(typeToConvert)); - } + return typeToConvert == typeof(OpenIddictMessage) ? new OpenIddictMessage(document.RootElement.Clone()) : + typeToConvert == typeof(OpenIddictRequest) ? new OpenIddictRequest(document.RootElement.Clone()) : + typeToConvert == typeof(OpenIddictResponse) ? new OpenIddictResponse(document.RootElement.Clone()) : + throw new ArgumentException(SR.GetResourceString(SR.ID0176), nameof(typeToConvert)); + } - /// - /// Serializes an OpenIddict primitive. - /// - /// The JSON writer. - /// The instance. - /// The JSON serializer options. - public override void Write(Utf8JsonWriter writer, OpenIddictMessage value, JsonSerializerOptions options) + /// + /// Serializes an OpenIddict primitive. + /// + /// The JSON writer. + /// The instance. + /// The JSON serializer options. + public override void Write(Utf8JsonWriter writer, OpenIddictMessage value, JsonSerializerOptions options) + { + if (writer is null) { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } - - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } + throw new ArgumentNullException(nameof(writer)); + } - value.WriteTo(writer); + if (value is null) + { + throw new ArgumentNullException(nameof(value)); } + + value.WriteTo(writer); } } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs index 65a239de..2e1ae6e9 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs @@ -19,1747 +19,1746 @@ using Microsoft.Extensions.Primitives; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides extension methods to make +/// and easier to work with. +/// +public static class OpenIddictExtensions { /// - /// Provides extension methods to make - /// and easier to work with. + /// Extracts the authentication context class values from an . /// - public static class OpenIddictExtensions + /// The instance. + public static ImmutableArray GetAcrValues(this OpenIddictRequest request) { - /// - /// Extracts the authentication context class values from an . - /// - /// The instance. - public static ImmutableArray GetAcrValues(this OpenIddictRequest request) + if (request is null) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } - - return GetValues(request.AcrValues, Separators.Space); + throw new ArgumentNullException(nameof(request)); } - /// - /// Extracts the prompt values from an . - /// - /// The instance. - public static ImmutableArray GetPrompts(this OpenIddictRequest request) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + return GetValues(request.AcrValues, Separators.Space); + } - return GetValues(request.Prompt, Separators.Space); + /// + /// Extracts the prompt values from an . + /// + /// The instance. + public static ImmutableArray GetPrompts(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Extracts the response types from an . - /// - /// The instance. - public static ImmutableArray GetResponseTypes(this OpenIddictRequest request) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + return GetValues(request.Prompt, Separators.Space); + } - return GetValues(request.ResponseType, Separators.Space); + /// + /// Extracts the response types from an . + /// + /// The instance. + public static ImmutableArray GetResponseTypes(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Extracts the scopes from an . - /// - /// The instance. - public static ImmutableArray GetScopes(this OpenIddictRequest request) + return GetValues(request.ResponseType, Separators.Space); + } + + /// + /// Extracts the scopes from an . + /// + /// The instance. + public static ImmutableArray GetScopes(this OpenIddictRequest request) + { + if (request is null) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + throw new ArgumentNullException(nameof(request)); + } - return GetValues(request.Scope, Separators.Space); + return GetValues(request.Scope, Separators.Space); + } + + /// + /// Determines whether the requested authentication context class values contain the specified item. + /// + /// The instance. + /// The component to look for in the parameter. + public static bool HasAcrValue(this OpenIddictRequest request, string value) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the requested authentication context class values contain the specified item. - /// - /// The instance. - /// The component to look for in the parameter. - public static bool HasAcrValue(this OpenIddictRequest request, string value) + if (string.IsNullOrEmpty(value)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0177), nameof(value)); + } - if (string.IsNullOrEmpty(value)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0177), nameof(value)); - } + return HasValue(request.AcrValues, value, Separators.Space); + } - return HasValue(request.AcrValues, value, Separators.Space); + /// + /// Determines whether the requested prompt contains the specified value. + /// + /// The instance. + /// The component to look for in the parameter. + public static bool HasPrompt(this OpenIddictRequest request, string prompt) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the requested prompt contains the specified value. - /// - /// The instance. - /// The component to look for in the parameter. - public static bool HasPrompt(this OpenIddictRequest request, string prompt) + if (string.IsNullOrEmpty(prompt)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0178), nameof(prompt)); + } - if (string.IsNullOrEmpty(prompt)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0178), nameof(prompt)); - } + return HasValue(request.Prompt, prompt, Separators.Space); + } - return HasValue(request.Prompt, prompt, Separators.Space); + /// + /// Determines whether the requested response type contains the specified value. + /// + /// The instance. + /// The component to look for in the parameter. + public static bool HasResponseType(this OpenIddictRequest request, string type) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the requested response type contains the specified value. - /// - /// The instance. - /// The component to look for in the parameter. - public static bool HasResponseType(this OpenIddictRequest request, string type) + if (string.IsNullOrEmpty(type)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0179), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0179), nameof(type)); - } + return HasValue(request.ResponseType, type, Separators.Space); + } - return HasValue(request.ResponseType, type, Separators.Space); + /// + /// Determines whether the requested scope contains the specified value. + /// + /// The instance. + /// The component to look for in the parameter. + public static bool HasScope(this OpenIddictRequest request, string scope) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the requested scope contains the specified value. - /// - /// The instance. - /// The component to look for in the parameter. - public static bool HasScope(this OpenIddictRequest request, string scope) + if (string.IsNullOrEmpty(scope)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0180), nameof(scope)); + } - if (string.IsNullOrEmpty(scope)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0180), nameof(scope)); - } + return HasValue(request.Scope, scope, Separators.Space); + } - return HasValue(request.Scope, scope, Separators.Space); + /// + /// Determines whether the "response_type" parameter corresponds to the "none" response type. + /// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#none for more information. + /// + /// The instance. + /// true if the request is a response_type=none request, false otherwise. + public static bool IsNoneFlow(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the "response_type" parameter corresponds to the "none" response type. - /// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#none for more information. - /// - /// The instance. - /// true if the request is a response_type=none request, false otherwise. - public static bool IsNoneFlow(this OpenIddictRequest request) + if (string.IsNullOrEmpty(request.ResponseType)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + return false; + } - if (string.IsNullOrEmpty(request.ResponseType)) - { - return false; - } + var segment = Trim(new StringSegment(request.ResponseType), Separators.Space); + if (segment.Length == 0) + { + return false; + } - var segment = Trim(new StringSegment(request.ResponseType), Separators.Space); - if (segment.Length == 0) - { - return false; - } + return segment.Equals(ResponseTypes.None, StringComparison.Ordinal); + } - return segment.Equals(ResponseTypes.None, StringComparison.Ordinal); + /// + /// Determines whether the "response_type" parameter corresponds to the authorization code flow. + /// See http://tools.ietf.org/html/rfc6749#section-4.1.1 for more information. + /// + /// The instance. + /// true if the request is a code flow request, false otherwise. + public static bool IsAuthorizationCodeFlow(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the "response_type" parameter corresponds to the authorization code flow. - /// See http://tools.ietf.org/html/rfc6749#section-4.1.1 for more information. - /// - /// The instance. - /// true if the request is a code flow request, false otherwise. - public static bool IsAuthorizationCodeFlow(this OpenIddictRequest request) + if (string.IsNullOrEmpty(request.ResponseType)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + return false; + } - if (string.IsNullOrEmpty(request.ResponseType)) - { - return false; - } + var segment = Trim(new StringSegment(request.ResponseType), Separators.Space); + if (segment.Length == 0) + { + return false; + } - var segment = Trim(new StringSegment(request.ResponseType), Separators.Space); - if (segment.Length == 0) - { - return false; - } + return segment.Equals(ResponseTypes.Code, StringComparison.Ordinal); + } - return segment.Equals(ResponseTypes.Code, StringComparison.Ordinal); + /// + /// Determines whether the "response_type" parameter corresponds to the implicit flow. + /// See http://tools.ietf.org/html/rfc6749#section-4.2.1 and + /// http://openid.net/specs/openid-connect-core-1_0.html for more information + /// + /// The instance. + /// true if the request is an implicit flow request, false otherwise. + public static bool IsImplicitFlow(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (string.IsNullOrEmpty(request.ResponseType)) + { + return false; } - /// - /// Determines whether the "response_type" parameter corresponds to the implicit flow. - /// See http://tools.ietf.org/html/rfc6749#section-4.2.1 and - /// http://openid.net/specs/openid-connect-core-1_0.html for more information - /// - /// The instance. - /// true if the request is an implicit flow request, false otherwise. - public static bool IsImplicitFlow(this OpenIddictRequest request) + var flags = /* none: */ 0x00; + + foreach (var element in new StringTokenizer(request.ResponseType, Separators.Space)) { - if (request is null) + var segment = Trim(element, Separators.Space); + if (segment.Length == 0) { - throw new ArgumentNullException(nameof(request)); + continue; } - if (string.IsNullOrEmpty(request.ResponseType)) + if (segment.Equals(ResponseTypes.IdToken, StringComparison.Ordinal)) { - return false; - } + flags |= /* id_token: */ 0x01; - var flags = /* none: */ 0x00; + continue; + } - foreach (var element in new StringTokenizer(request.ResponseType, Separators.Space)) + // Note: though the OIDC core specs does not include the OAuth 2.0-inherited response_type=token, + // it is considered as a valid response_type for the implicit flow for backward compatibility. + else if (segment.Equals(ResponseTypes.Token, StringComparison.Ordinal)) { - var segment = Trim(element, Separators.Space); - if (segment.Length == 0) - { - continue; - } - - if (segment.Equals(ResponseTypes.IdToken, StringComparison.Ordinal)) - { - flags |= /* id_token: */ 0x01; + flags |= /* token */ 0x02; - continue; - } + continue; + } - // Note: though the OIDC core specs does not include the OAuth 2.0-inherited response_type=token, - // it is considered as a valid response_type for the implicit flow for backward compatibility. - else if (segment.Equals(ResponseTypes.Token, StringComparison.Ordinal)) - { - flags |= /* token */ 0x02; + // Always return false if the response_type item + // is not a valid component for the implicit flow. + return false; + } - continue; - } + // Return true if the response_type parameter contains "id_token" or "token". + return (flags & /* id_token: */ 0x01) == 0x01 || (flags & /* token: */ 0x02) == 0x02; + } - // Always return false if the response_type item - // is not a valid component for the implicit flow. - return false; - } + /// + /// Determines whether the "response_type" parameter corresponds to the hybrid flow. + /// See http://tools.ietf.org/html/rfc6749#section-4.2.1 and + /// http://openid.net/specs/openid-connect-core-1_0.html for more information. + /// + /// The instance. + /// true if the request is an hybrid flow request, false otherwise. + public static bool IsHybridFlow(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } - // Return true if the response_type parameter contains "id_token" or "token". - return (flags & /* id_token: */ 0x01) == 0x01 || (flags & /* token: */ 0x02) == 0x02; + if (string.IsNullOrEmpty(request.ResponseType)) + { + return false; } - /// - /// Determines whether the "response_type" parameter corresponds to the hybrid flow. - /// See http://tools.ietf.org/html/rfc6749#section-4.2.1 and - /// http://openid.net/specs/openid-connect-core-1_0.html for more information. - /// - /// The instance. - /// true if the request is an hybrid flow request, false otherwise. - public static bool IsHybridFlow(this OpenIddictRequest request) + var flags = /* none */ 0x00; + + foreach (var element in new StringTokenizer(request.ResponseType, Separators.Space)) { - if (request is null) + var segment = Trim(element, Separators.Space); + if (segment.Length == 0) { - throw new ArgumentNullException(nameof(request)); + continue; } - if (string.IsNullOrEmpty(request.ResponseType)) + if (segment.Equals(ResponseTypes.Code, StringComparison.Ordinal)) { - return false; - } + flags |= /* code: */ 0x01; - var flags = /* none */ 0x00; + continue; + } - foreach (var element in new StringTokenizer(request.ResponseType, Separators.Space)) + else if (segment.Equals(ResponseTypes.IdToken, StringComparison.Ordinal)) { - var segment = Trim(element, Separators.Space); - if (segment.Length == 0) - { - continue; - } - - if (segment.Equals(ResponseTypes.Code, StringComparison.Ordinal)) - { - flags |= /* code: */ 0x01; - - continue; - } - - else if (segment.Equals(ResponseTypes.IdToken, StringComparison.Ordinal)) - { - flags |= /* id_token: */ 0x02; - - continue; - } - - else if (segment.Equals(ResponseTypes.Token, StringComparison.Ordinal)) - { - flags |= /* token: */ 0x04; - - continue; - } + flags |= /* id_token: */ 0x02; - // Always return false if the response_type item - // is not a valid component for the hybrid flow. - return false; + continue; } - // Return false if the response_type parameter doesn't contain "code". - if ((flags & /* code: */ 0x01) != 0x01) + else if (segment.Equals(ResponseTypes.Token, StringComparison.Ordinal)) { - return false; + flags |= /* token: */ 0x04; + + continue; } - // Return true if the response_type parameter contains "id_token" or "token". - return (flags & /* id_token: */ 0x02) == 0x02 || (flags & /* token: */ 0x04) == 0x04; + // Always return false if the response_type item + // is not a valid component for the hybrid flow. + return false; } - /// - /// Determines whether the "response_mode" parameter corresponds to the fragment response mode. - /// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html for more information. - /// - /// The instance. - /// - /// true if the request specified the fragment response mode or if - /// it's the default value for the requested flow, false otherwise. - /// - public static bool IsFragmentResponseMode(this OpenIddictRequest request) + // Return false if the response_type parameter doesn't contain "code". + if ((flags & /* code: */ 0x01) != 0x01) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } - - if (string.Equals(request.ResponseMode, ResponseModes.Fragment, StringComparison.Ordinal)) - { - return true; - } + return false; + } - // Don't guess the response_mode value - // if an explicit value has been provided. - if (!string.IsNullOrEmpty(request.ResponseMode)) - { - return false; - } + // Return true if the response_type parameter contains "id_token" or "token". + return (flags & /* id_token: */ 0x02) == 0x02 || (flags & /* token: */ 0x04) == 0x04; + } - // Both the implicit and the hybrid flows - // use response_mode=fragment by default. - return request.IsImplicitFlow() || request.IsHybridFlow(); + /// + /// Determines whether the "response_mode" parameter corresponds to the fragment response mode. + /// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html for more information. + /// + /// The instance. + /// + /// true if the request specified the fragment response mode or if + /// it's the default value for the requested flow, false otherwise. + /// + public static bool IsFragmentResponseMode(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the "response_mode" parameter corresponds to the query response mode. - /// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html for more information. - /// - /// The instance. - /// - /// true if the request specified the query response mode or if - /// it's the default value for the requested flow, false otherwise. - /// - public static bool IsQueryResponseMode(this OpenIddictRequest request) + if (string.Equals(request.ResponseMode, ResponseModes.Fragment, StringComparison.Ordinal)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + return true; + } - if (string.Equals(request.ResponseMode, ResponseModes.Query, StringComparison.Ordinal)) - { - return true; - } + // Don't guess the response_mode value + // if an explicit value has been provided. + if (!string.IsNullOrEmpty(request.ResponseMode)) + { + return false; + } - // Don't guess the response_mode value - // if an explicit value has been provided. - if (!string.IsNullOrEmpty(request.ResponseMode)) - { - return false; - } + // Both the implicit and the hybrid flows + // use response_mode=fragment by default. + return request.IsImplicitFlow() || request.IsHybridFlow(); + } - // Code flow and "response_type=none" use response_mode=query by default. - return request.IsAuthorizationCodeFlow() || request.IsNoneFlow(); + /// + /// Determines whether the "response_mode" parameter corresponds to the query response mode. + /// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html for more information. + /// + /// The instance. + /// + /// true if the request specified the query response mode or if + /// it's the default value for the requested flow, false otherwise. + /// + public static bool IsQueryResponseMode(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the "response_mode" parameter corresponds to the form post response mode. - /// See http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html for more information. - /// - /// The instance. - /// - /// true if the request specified the form post response mode or if - /// it's the default value for the requested flow, false otherwise. - /// - public static bool IsFormPostResponseMode(this OpenIddictRequest request) + if (string.Equals(request.ResponseMode, ResponseModes.Query, StringComparison.Ordinal)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } - - return string.Equals(request.ResponseMode, ResponseModes.FormPost, StringComparison.Ordinal); + return true; } - /// - /// Determines whether the "grant_type" parameter corresponds to the authorization code grant. - /// See http://tools.ietf.org/html/rfc6749#section-4.1.3 for more information. - /// - /// The instance. - /// true if the request is a code grant request, false otherwise. - public static bool IsAuthorizationCodeGrantType(this OpenIddictRequest request) + // Don't guess the response_mode value + // if an explicit value has been provided. + if (!string.IsNullOrEmpty(request.ResponseMode)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } - - return string.Equals(request.GrantType, GrantTypes.AuthorizationCode, StringComparison.Ordinal); + return false; } - /// - /// Determines whether the "grant_type" parameter corresponds to the client credentials grant. - /// See http://tools.ietf.org/html/rfc6749#section-4.4.2 for more information. - /// - /// The instance. - /// true if the request is a client credentials grant request, false otherwise. - public static bool IsClientCredentialsGrantType(this OpenIddictRequest request) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + // Code flow and "response_type=none" use response_mode=query by default. + return request.IsAuthorizationCodeFlow() || request.IsNoneFlow(); + } - return string.Equals(request.GrantType, GrantTypes.ClientCredentials, StringComparison.Ordinal); + /// + /// Determines whether the "response_mode" parameter corresponds to the form post response mode. + /// See http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html for more information. + /// + /// The instance. + /// + /// true if the request specified the form post response mode or if + /// it's the default value for the requested flow, false otherwise. + /// + public static bool IsFormPostResponseMode(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the "grant_type" parameter corresponds to the device code grant. - /// See https://tools.ietf.org/html/rfc8628 for more information. - /// - /// The instance. - /// true if the request is a device code grant request, false otherwise. - public static bool IsDeviceCodeGrantType(this OpenIddictRequest request) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + return string.Equals(request.ResponseMode, ResponseModes.FormPost, StringComparison.Ordinal); + } - return string.Equals(request.GrantType, GrantTypes.DeviceCode, StringComparison.Ordinal); + /// + /// Determines whether the "grant_type" parameter corresponds to the authorization code grant. + /// See http://tools.ietf.org/html/rfc6749#section-4.1.3 for more information. + /// + /// The instance. + /// true if the request is a code grant request, false otherwise. + public static bool IsAuthorizationCodeGrantType(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the "grant_type" parameter corresponds to the password grant. - /// See http://tools.ietf.org/html/rfc6749#section-4.3.2 for more information. - /// - /// The instance. - /// true if the request is a password grant request, false otherwise. - public static bool IsPasswordGrantType(this OpenIddictRequest request) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + return string.Equals(request.GrantType, GrantTypes.AuthorizationCode, StringComparison.Ordinal); + } - return string.Equals(request.GrantType, GrantTypes.Password, StringComparison.Ordinal); + /// + /// Determines whether the "grant_type" parameter corresponds to the client credentials grant. + /// See http://tools.ietf.org/html/rfc6749#section-4.4.2 for more information. + /// + /// The instance. + /// true if the request is a client credentials grant request, false otherwise. + public static bool IsClientCredentialsGrantType(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Determines whether the "grant_type" parameter corresponds to the refresh token grant. - /// See http://tools.ietf.org/html/rfc6749#section-6 for more information. - /// - /// The instance. - /// true if the request is a refresh token grant request, false otherwise. - public static bool IsRefreshTokenGrantType(this OpenIddictRequest request) - { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + return string.Equals(request.GrantType, GrantTypes.ClientCredentials, StringComparison.Ordinal); + } - return string.Equals(request.GrantType, GrantTypes.RefreshToken, StringComparison.Ordinal); + /// + /// Determines whether the "grant_type" parameter corresponds to the device code grant. + /// See https://tools.ietf.org/html/rfc8628 for more information. + /// + /// The instance. + /// true if the request is a device code grant request, false otherwise. + public static bool IsDeviceCodeGrantType(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Gets the destinations associated with a claim. - /// - /// The instance. - /// The destinations associated with the claim. - public static ImmutableArray GetDestinations(this Claim claim) + return string.Equals(request.GrantType, GrantTypes.DeviceCode, StringComparison.Ordinal); + } + + /// + /// Determines whether the "grant_type" parameter corresponds to the password grant. + /// See http://tools.ietf.org/html/rfc6749#section-4.3.2 for more information. + /// + /// The instance. + /// true if the request is a password grant request, false otherwise. + public static bool IsPasswordGrantType(this OpenIddictRequest request) + { + if (request is null) { - if (claim is null) - { - throw new ArgumentNullException(nameof(claim)); - } + throw new ArgumentNullException(nameof(request)); + } - claim.Properties.TryGetValue(Properties.Destinations, out string? destinations); + return string.Equals(request.GrantType, GrantTypes.Password, StringComparison.Ordinal); + } - if (string.IsNullOrEmpty(destinations)) - { - return ImmutableArray.Create(); - } + /// + /// Determines whether the "grant_type" parameter corresponds to the refresh token grant. + /// See http://tools.ietf.org/html/rfc6749#section-6 for more information. + /// + /// The instance. + /// true if the request is a refresh token grant request, false otherwise. + public static bool IsRefreshTokenGrantType(this OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } - using var document = JsonDocument.Parse(destinations); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); + return string.Equals(request.GrantType, GrantTypes.RefreshToken, StringComparison.Ordinal); + } - foreach (var element in document.RootElement.EnumerateArray()) - { - var value = element.GetString(); - if (string.IsNullOrEmpty(value) || builder.Contains(value, StringComparer.OrdinalIgnoreCase)) - { - continue; - } + /// + /// Gets the destinations associated with a claim. + /// + /// The instance. + /// The destinations associated with the claim. + public static ImmutableArray GetDestinations(this Claim claim) + { + if (claim is null) + { + throw new ArgumentNullException(nameof(claim)); + } - builder.Add(value); - } + claim.Properties.TryGetValue(Properties.Destinations, out string? destinations); - return builder.ToImmutable(); + if (string.IsNullOrEmpty(destinations)) + { + return ImmutableArray.Create(); } - /// - /// Determines whether the given claim contains the required destination. - /// - /// The instance. - /// The required destination. - public static bool HasDestination(this Claim claim, string destination) + using var document = JsonDocument.Parse(destinations); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); + + foreach (var element in document.RootElement.EnumerateArray()) { - if (claim is null) + var value = element.GetString(); + if (string.IsNullOrEmpty(value) || builder.Contains(value, StringComparer.OrdinalIgnoreCase)) { - throw new ArgumentNullException(nameof(claim)); + continue; } - if (string.IsNullOrEmpty(destination)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0181), nameof(destination)); - } + builder.Add(value); + } - claim.Properties.TryGetValue(Properties.Destinations, out string? destinations); + return builder.ToImmutable(); + } - if (string.IsNullOrEmpty(destinations)) - { - return false; - } + /// + /// Determines whether the given claim contains the required destination. + /// + /// The instance. + /// The required destination. + public static bool HasDestination(this Claim claim, string destination) + { + if (claim is null) + { + throw new ArgumentNullException(nameof(claim)); + } - using var document = JsonDocument.Parse(destinations); + if (string.IsNullOrEmpty(destination)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0181), nameof(destination)); + } - foreach (var element in document.RootElement.EnumerateArray()) - { - var value = element.GetString(); - if (string.Equals(value, destination, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } + claim.Properties.TryGetValue(Properties.Destinations, out string? destinations); + if (string.IsNullOrEmpty(destinations)) + { return false; } - /// - /// Adds specific destinations to a claim. - /// - /// The instance. - /// The destinations. - public static Claim SetDestinations(this Claim claim, ImmutableArray destinations) + using var document = JsonDocument.Parse(destinations); + + foreach (var element in document.RootElement.EnumerateArray()) { - if (claim is null) + var value = element.GetString(); + if (string.Equals(value, destination, StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentNullException(nameof(claim)); + return true; } + } - if (destinations.IsDefaultOrEmpty) - { - claim.Properties.Remove(Properties.Destinations); + return false; + } - return claim; - } + /// + /// Adds specific destinations to a claim. + /// + /// The instance. + /// The destinations. + public static Claim SetDestinations(this Claim claim, ImmutableArray destinations) + { + if (claim is null) + { + throw new ArgumentNullException(nameof(claim)); + } - if (destinations.Any(destination => string.IsNullOrEmpty(destination))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0182), nameof(destinations)); - } + if (destinations.IsDefaultOrEmpty) + { + claim.Properties.Remove(Properties.Destinations); - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + return claim; + } - writer.WriteStartArray(); + if (destinations.Any(destination => string.IsNullOrEmpty(destination))) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0182), nameof(destinations)); + } - foreach (var destination in destinations.Distinct(StringComparer.OrdinalIgnoreCase)) - { - writer.WriteStringValue(destination); - } + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - writer.WriteEndArray(); - writer.Flush(); + writer.WriteStartArray(); - claim.Properties[Properties.Destinations] = Encoding.UTF8.GetString(stream.ToArray()); + foreach (var destination in destinations.Distinct(StringComparer.OrdinalIgnoreCase)) + { + writer.WriteStringValue(destination); + } - return claim; + writer.WriteEndArray(); + writer.Flush(); + + claim.Properties[Properties.Destinations] = Encoding.UTF8.GetString(stream.ToArray()); + + return claim; + } + + /// + /// Adds specific destinations to a claim. + /// + /// The instance. + /// The destinations. + public static Claim SetDestinations(this Claim claim, IEnumerable? destinations) + => claim.SetDestinations(destinations?.ToImmutableArray() ?? ImmutableArray.Create()); + + /// + /// Adds specific destinations to a claim. + /// + /// The instance. + /// The destinations. + public static Claim SetDestinations(this Claim claim, params string[]? destinations) + => claim.SetDestinations(destinations?.ToImmutableArray() ?? ImmutableArray.Create()); + + /// + /// Gets the destinations associated with all the claims of the given principal. + /// + /// The principal. + /// The destinations, returned as a flattened dictionary. + public static ImmutableDictionary GetDestinations(this ClaimsPrincipal principal) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Adds specific destinations to a claim. - /// - /// The instance. - /// The destinations. - public static Claim SetDestinations(this Claim claim, IEnumerable? destinations) - => claim.SetDestinations(destinations?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Adds specific destinations to a claim. - /// - /// The instance. - /// The destinations. - public static Claim SetDestinations(this Claim claim, params string[]? destinations) - => claim.SetDestinations(destinations?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Gets the destinations associated with all the claims of the given principal. - /// - /// The principal. - /// The destinations, returned as a flattened dictionary. - public static ImmutableDictionary GetDestinations(this ClaimsPrincipal principal) - { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); - var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); + foreach (var group in principal.Claims.GroupBy(claim => claim.Type)) + { + var claims = group.ToList(); - foreach (var group in principal.Claims.GroupBy(claim => claim.Type)) + var destinations = new HashSet(claims[0].GetDestinations(), StringComparer.OrdinalIgnoreCase); + if (destinations.Count != 0) { - var claims = group.ToList(); - - var destinations = new HashSet(claims[0].GetDestinations(), StringComparer.OrdinalIgnoreCase); - if (destinations.Count != 0) + // Ensure the other claims of the same type use the same exact destinations. + for (var index = 0; index < claims.Count; index++) { - // Ensure the other claims of the same type use the same exact destinations. - for (var index = 0; index < claims.Count; index++) + if (!destinations.SetEquals(claims[index].GetDestinations())) { - if (!destinations.SetEquals(claims[index].GetDestinations())) - { - throw new InvalidOperationException(SR.FormatID0183(group.Key)); - } + throw new InvalidOperationException(SR.FormatID0183(group.Key)); } - - builder.Add(group.Key, destinations.ToArray()); } + + builder.Add(group.Key, destinations.ToArray()); } + } + + return builder.ToImmutable(); + } - return builder.ToImmutable(); + /// + /// Sets the destinations associated with all the claims of the given principal. + /// + /// The principal. + /// The destinations, as a flattened dictionary. + /// The principal. + public static ClaimsPrincipal SetDestinations(this ClaimsPrincipal principal, ImmutableDictionary destinations) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Sets the destinations associated with all the claims of the given principal. - /// - /// The principal. - /// The destinations, as a flattened dictionary. - /// The principal. - public static ClaimsPrincipal SetDestinations(this ClaimsPrincipal principal, ImmutableDictionary destinations) + if (destinations is null) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentNullException(nameof(destinations)); + } - if (destinations is null) + foreach (var destination in destinations) + { + foreach (var claim in principal.Claims.Where(claim => claim.Type == destination.Key)) { - throw new ArgumentNullException(nameof(destinations)); + claim.SetDestinations(destination.Value); } + } - foreach (var destination in destinations) - { - foreach (var claim in principal.Claims.Where(claim => claim.Type == destination.Key)) - { - claim.SetDestinations(destination.Value); - } - } + return principal; + } - return principal; + /// + /// Clones an identity by filtering its claims and the claims of its actor, recursively. + /// + /// The instance to filter. + /// + /// The delegate filtering the claims: return true + /// to accept the claim, false to remove it. + /// + public static ClaimsIdentity Clone(this ClaimsIdentity identity, Func filter) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Clones an identity by filtering its claims and the claims of its actor, recursively. - /// - /// The instance to filter. - /// - /// The delegate filtering the claims: return true - /// to accept the claim, false to remove it. - /// - public static ClaimsIdentity Clone(this ClaimsIdentity identity, Func filter) + if (filter is null) { - if (identity is null) - { - throw new ArgumentNullException(nameof(identity)); - } - - if (filter is null) - { - throw new ArgumentNullException(nameof(filter)); - } + throw new ArgumentNullException(nameof(filter)); + } - var clone = identity.Clone(); + var clone = identity.Clone(); - // Note: make sure to call ToList() to avoid modifying - // the initial collection iterated by ClaimsIdentity.Claims. - foreach (var claim in clone.Claims.ToList()) + // Note: make sure to call ToList() to avoid modifying + // the initial collection iterated by ClaimsIdentity.Claims. + foreach (var claim in clone.Claims.ToList()) + { + if (!filter(claim)) { - if (!filter(claim)) - { - clone.RemoveClaim(claim); - } + clone.RemoveClaim(claim); } + } - if (clone.Actor is not null) - { - clone.Actor = clone.Actor.Clone(filter); - } + if (clone.Actor is not null) + { + clone.Actor = clone.Actor.Clone(filter); + } + + return clone; + } - return clone; + /// + /// Clones a principal by filtering its identities. + /// + /// The instance to filter. + /// + /// The delegate filtering the claims: return true + /// to accept the claim, false to remove it. + /// + public static ClaimsPrincipal Clone(this ClaimsPrincipal principal, Func filter) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Clones a principal by filtering its identities. - /// - /// The instance to filter. - /// - /// The delegate filtering the claims: return true - /// to accept the claim, false to remove it. - /// - public static ClaimsPrincipal Clone(this ClaimsPrincipal principal, Func filter) + if (filter is null) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentNullException(nameof(filter)); + } - if (filter is null) - { - throw new ArgumentNullException(nameof(filter)); - } + var clone = new ClaimsPrincipal(); - var clone = new ClaimsPrincipal(); + foreach (var identity in principal.Identities) + { + clone.AddIdentity(identity.Clone(filter)); + } - foreach (var identity in principal.Identities) - { - clone.AddIdentity(identity.Clone(filter)); - } + return clone; + } - return clone; + /// + /// Adds a claim to a given identity. + /// + /// The identity. + /// The type associated with the claim. + /// The value associated with the claim. + public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, string value) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Adds a claim to a given identity. - /// - /// The identity. - /// The type associated with the claim. - /// The value associated with the claim. - public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, string value) + if (string.IsNullOrEmpty(type)) { - if (identity is null) - { - throw new ArgumentNullException(nameof(identity)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); + } - if (string.IsNullOrEmpty(value)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); - } + identity.AddClaim(new Claim(type, value)); + return identity; + } - identity.AddClaim(new Claim(type, value)); - return identity; + /// + /// Adds a claim to a given identity and specify one or more destinations. + /// + /// The identity. + /// The type associated with the claim. + /// The value associated with the claim. + /// The destinations associated with the claim. + public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, string value, ImmutableArray destinations) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Adds a claim to a given identity and specify one or more destinations. - /// - /// The identity. - /// The type associated with the claim. - /// The value associated with the claim. - /// The destinations associated with the claim. - public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, string value, ImmutableArray destinations) + if (string.IsNullOrEmpty(type)) { - if (identity is null) - { - throw new ArgumentNullException(nameof(identity)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(value)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); - } + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0185), nameof(value)); + } - identity.AddClaim(new Claim(type, value).SetDestinations(destinations)); - return identity; - } - - /// - /// Adds a claim to a given identity and specify one or more destinations. - /// - /// The identity. - /// The type associated with the claim. - /// The value associated with the claim. - /// The destinations associated with the claim. - public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, string value, params string[]? destinations) - => identity.AddClaim(type, value, destinations?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Gets the claim value corresponding to the given type. - /// - /// The identity. - /// The type associated with the claim. - /// The claim value. - public static string? GetClaim(this ClaimsIdentity identity, string type) - { - if (identity is null) - { - throw new ArgumentNullException(nameof(identity)); - } + identity.AddClaim(new Claim(type, value).SetDestinations(destinations)); + return identity; + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + /// + /// Adds a claim to a given identity and specify one or more destinations. + /// + /// The identity. + /// The type associated with the claim. + /// The value associated with the claim. + /// The destinations associated with the claim. + public static ClaimsIdentity AddClaim(this ClaimsIdentity identity, string type, string value, params string[]? destinations) + => identity.AddClaim(type, value, destinations?.ToImmutableArray() ?? ImmutableArray.Create()); - return identity.FindFirst(type)?.Value; + /// + /// Gets the claim value corresponding to the given type. + /// + /// The identity. + /// The type associated with the claim. + /// The claim value. + public static string? GetClaim(this ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Gets the claim value corresponding to the given type. - /// - /// The principal. - /// The type associated with the claim. - /// The claim value. - public static string? GetClaim(this ClaimsPrincipal principal, string type) + if (string.IsNullOrEmpty(type)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + return identity.FindFirst(type)?.Value; + } - return principal.FindFirst(type)?.Value; + /// + /// Gets the claim value corresponding to the given type. + /// + /// The principal. + /// The type associated with the claim. + /// The claim value. + public static string? GetClaim(this ClaimsPrincipal principal, string type) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Gets the claim values corresponding to the given type. - /// - /// The identity. - /// The type associated with the claims. - /// The claim values. - public static ImmutableArray GetClaims(this ClaimsIdentity identity, string type) + if (string.IsNullOrEmpty(type)) { - if (identity is null) - { - throw new ArgumentNullException(nameof(identity)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + return principal.FindFirst(type)?.Value; + } - return identity.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray(); + /// + /// Gets the claim values corresponding to the given type. + /// + /// The identity. + /// The type associated with the claims. + /// The claim values. + public static ImmutableArray GetClaims(this ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Determines whether the claims identity contains at least one claim of the specified type. - /// - /// The claims identity. - /// The claim type. - /// true if the identity contains at least one claim of the specified type. - public static bool HasClaim(this ClaimsIdentity identity, string type) + if (string.IsNullOrEmpty(type)) { - if (identity is null) - { - throw new ArgumentNullException(nameof(identity)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + return identity.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray(); + } - return identity.FindAll(type).Any(); + /// + /// Determines whether the claims identity contains at least one claim of the specified type. + /// + /// The claims identity. + /// The claim type. + /// true if the identity contains at least one claim of the specified type. + public static bool HasClaim(this ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Gets the claim values corresponding to the given type. - /// - /// The principal. - /// The type associated with the claims. - /// The claim values. - public static ImmutableArray GetClaims(this ClaimsPrincipal principal, string type) + if (string.IsNullOrEmpty(type)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + return identity.FindAll(type).Any(); + } - return principal.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray(); + /// + /// Gets the claim values corresponding to the given type. + /// + /// The principal. + /// The type associated with the claims. + /// The claim values. + public static ImmutableArray GetClaims(this ClaimsPrincipal principal, string type) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Determines whether the claims principal contains at least one claim of the specified type. - /// - /// The claims principal. - /// The claim type. - /// true if the principal contains at least one claim of the specified type. - public static bool HasClaim(this ClaimsPrincipal principal, string type) + if (string.IsNullOrEmpty(type)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + return principal.FindAll(type).Select(claim => claim.Value).Distinct(StringComparer.Ordinal).ToImmutableArray(); + } - return principal.FindAll(type).Any(); + /// + /// Determines whether the claims principal contains at least one claim of the specified type. + /// + /// The claims principal. + /// The claim type. + /// true if the principal contains at least one claim of the specified type. + public static bool HasClaim(this ClaimsPrincipal principal, string type) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Removes all the claims corresponding to the given type. - /// - /// The identity. - /// The type associated with the claims. - /// The claims identity. - public static ClaimsIdentity RemoveClaims(this ClaimsIdentity identity, string type) + if (string.IsNullOrEmpty(type)) { - if (identity is null) - { - throw new ArgumentNullException(nameof(identity)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - foreach (var claim in identity.FindAll(type).ToList()) - { - identity.RemoveClaim(claim); - } + return principal.FindAll(type).Any(); + } - return identity; + /// + /// Removes all the claims corresponding to the given type. + /// + /// The identity. + /// The type associated with the claims. + /// The claims identity. + public static ClaimsIdentity RemoveClaims(this ClaimsIdentity identity, string type) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Removes all the claims corresponding to the given type. - /// - /// The principal. - /// The type associated with the claims. - /// The claims identity. - public static ClaimsPrincipal RemoveClaims(this ClaimsPrincipal principal, string type) + if (string.IsNullOrEmpty(type)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + foreach (var claim in identity.FindAll(type).ToList()) + { + identity.RemoveClaim(claim); + } - foreach (var identity in principal.Identities) - { - foreach (var claim in identity.FindAll(type).ToList()) - { - identity.RemoveClaim(claim); - } - } + return identity; + } - return principal; + /// + /// Removes all the claims corresponding to the given type. + /// + /// The principal. + /// The type associated with the claims. + /// The claims identity. + public static ClaimsPrincipal RemoveClaims(this ClaimsPrincipal principal, string type) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Sets the claim value corresponding to the given type. - /// - /// The identity. - /// The type associated with the claims. - /// The claim value. - /// The claims identity. - public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, string? value) + if (string.IsNullOrEmpty(type)) { - if (identity is null) - { - throw new ArgumentNullException(nameof(identity)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(type)) + foreach (var identity in principal.Identities) + { + foreach (var claim in identity.FindAll(type).ToList()) { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + identity.RemoveClaim(claim); } + } - identity.RemoveClaims(type); - - if (!string.IsNullOrEmpty(value)) - { - identity.AddClaim(type, value); - } + return principal; + } - return identity; + /// + /// Sets the claim value corresponding to the given type. + /// + /// The identity. + /// The type associated with the claims. + /// The claim value. + /// The claims identity. + public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, string? value) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Sets the claim value corresponding to the given type. - /// - /// The principal. - /// The type associated with the claims. - /// The claim value. - /// The claims identity. - public static ClaimsPrincipal SetClaim(this ClaimsPrincipal principal, string type, string? value) + if (string.IsNullOrEmpty(type)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (principal.Identity is not ClaimsIdentity identity) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0286), nameof(principal)); - } + identity.RemoveClaims(type); - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + if (!string.IsNullOrEmpty(value)) + { + identity.AddClaim(type, value); + } - principal.RemoveClaims(type); + return identity; + } - if (!string.IsNullOrEmpty(value)) - { - identity.AddClaim(type, value); - } + /// + /// Sets the claim value corresponding to the given type. + /// + /// The principal. + /// The type associated with the claims. + /// The claim value. + /// The claims identity. + public static ClaimsPrincipal SetClaim(this ClaimsPrincipal principal, string type, string? value) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } - return principal; + if (principal.Identity is not ClaimsIdentity identity) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0286), nameof(principal)); } - /// - /// Sets the claim values corresponding to the given type. - /// - /// The identity. - /// The type associated with the claims. - /// The claim values. - /// The claims identity. - public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, ImmutableArray values) + if (string.IsNullOrEmpty(type)) { - if (identity is null) - { - throw new ArgumentNullException(nameof(identity)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + principal.RemoveClaims(type); - identity.RemoveClaims(type); + if (!string.IsNullOrEmpty(value)) + { + identity.AddClaim(type, value); + } - foreach (var value in values.Distinct(StringComparer.Ordinal)) - { - identity.AddClaim(type, value); - } + return principal; + } - return identity; + /// + /// Sets the claim values corresponding to the given type. + /// + /// The identity. + /// The type associated with the claims. + /// The claim values. + /// The claims identity. + public static ClaimsIdentity SetClaims(this ClaimsIdentity identity, string type, ImmutableArray values) + { + if (identity is null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Sets the claim values corresponding to the given type. - /// - /// The principal. - /// The type associated with the claims. - /// The claim values. - /// The claims identity. - public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, ImmutableArray values) + if (string.IsNullOrEmpty(type)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - if (principal.Identity is not ClaimsIdentity identity) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0286), nameof(principal)); - } + identity.RemoveClaims(type); - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); - } + foreach (var value in values.Distinct(StringComparer.Ordinal)) + { + identity.AddClaim(type, value); + } - principal.RemoveClaims(type); + return identity; + } - foreach (var value in values.Distinct(StringComparer.Ordinal)) - { - identity.AddClaim(type, value); - } + /// + /// Sets the claim values corresponding to the given type. + /// + /// The principal. + /// The type associated with the claims. + /// The claim values. + /// The claims identity. + public static ClaimsPrincipal SetClaims(this ClaimsPrincipal principal, string type, ImmutableArray values) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } - return principal; + if (principal.Identity is not ClaimsIdentity identity) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0286), nameof(principal)); } - /// - /// Gets the creation date stored in the claims principal. - /// - /// The claims principal. - /// The creation date or null if the claim cannot be found. - public static DateTimeOffset? GetCreationDate(this ClaimsPrincipal principal) + if (string.IsNullOrEmpty(type)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0184), nameof(type)); + } - var claim = principal.FindFirst(Claims.Private.CreationDate); - if (claim is null) - { - return null; - } + principal.RemoveClaims(type); - if (!DateTimeOffset.TryParseExact(claim.Value, "r", CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) - { - return null; - } + foreach (var value in values.Distinct(StringComparer.Ordinal)) + { + identity.AddClaim(type, value); + } + + return principal; + } - return value; + /// + /// Gets the creation date stored in the claims principal. + /// + /// The claims principal. + /// The creation date or null if the claim cannot be found. + public static DateTimeOffset? GetCreationDate(this ClaimsPrincipal principal) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Gets the expiration date stored in the claims principal. - /// - /// The claims principal. - /// The expiration date or null if the claim cannot be found. - public static DateTimeOffset? GetExpirationDate(this ClaimsPrincipal principal) + var claim = principal.FindFirst(Claims.Private.CreationDate); + if (claim is null) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + return null; + } - var claim = principal.FindFirst(Claims.Private.ExpirationDate); - if (claim is null) - { - return null; - } + if (!DateTimeOffset.TryParseExact(claim.Value, "r", CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) + { + return null; + } - if (!DateTimeOffset.TryParseExact(claim.Value, "r", CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) - { - return null; - } + return value; + } - return value; - } - - /// - /// Gets the audiences list stored in the claims principal. - /// - /// The claims principal. - /// The audiences list or an empty set if the claims cannot be found. - public static ImmutableArray GetAudiences(this ClaimsPrincipal principal) - => principal.GetClaims(Claims.Private.Audience); - - /// - /// Gets the presenters list stored in the claims principal. - /// - /// The claims principal. - /// The presenters list or an empty set if the claims cannot be found. - public static ImmutableArray GetPresenters(this ClaimsPrincipal principal) - => principal.GetClaims(Claims.Private.Presenter); - - /// - /// Gets the resources list stored in the claims principal. - /// - /// The claims principal. - /// The resources list or an empty set if the claims cannot be found. - public static ImmutableArray GetResources(this ClaimsPrincipal principal) - => principal.GetClaims(Claims.Private.Resource); - - /// - /// Gets the scopes list stored in the claims principal. - /// - /// The claims principal. - /// The scopes list or an empty set if the claim cannot be found. - public static ImmutableArray GetScopes(this ClaimsPrincipal principal) - => principal.GetClaims(Claims.Private.Scope); - - /// - /// Gets the access token lifetime associated with the claims principal. - /// - /// The claims principal. - /// The access token lifetime or null if the claim cannot be found. - public static TimeSpan? GetAccessTokenLifetime(this ClaimsPrincipal principal) - => GetLifetime(principal, Claims.Private.AccessTokenLifetime); - - /// - /// Gets the authorization code lifetime associated with the claims principal. - /// - /// The claims principal. - /// The authorization code lifetime or null if the claim cannot be found. - public static TimeSpan? GetAuthorizationCodeLifetime(this ClaimsPrincipal principal) - => GetLifetime(principal, Claims.Private.AuthorizationCodeLifetime); - - /// - /// Gets the device code lifetime associated with the claims principal. - /// - /// The claims principal. - /// The device code lifetime or null if the claim cannot be found. - public static TimeSpan? GetDeviceCodeLifetime(this ClaimsPrincipal principal) - => GetLifetime(principal, Claims.Private.DeviceCodeLifetime); - - /// - /// Gets the identity token lifetime associated with the claims principal. - /// - /// The claims principal. - /// The identity token lifetime or null if the claim cannot be found. - public static TimeSpan? GetIdentityTokenLifetime(this ClaimsPrincipal principal) - => GetLifetime(principal, Claims.Private.IdentityTokenLifetime); - - /// - /// Gets the refresh token lifetime associated with the claims principal. - /// - /// The claims principal. - /// The refresh token lifetime or null if the claim cannot be found. - public static TimeSpan? GetRefreshTokenLifetime(this ClaimsPrincipal principal) - => GetLifetime(principal, Claims.Private.RefreshTokenLifetime); - - /// - /// Gets the user code lifetime associated with the claims principal. - /// - /// The claims principal. - /// The user code lifetime or null if the claim cannot be found. - public static TimeSpan? GetUserCodeLifetime(this ClaimsPrincipal principal) - => GetLifetime(principal, Claims.Private.UserCodeLifetime); - - /// - /// Gets the internal authorization identifier associated with the claims principal. - /// - /// The claims principal. - /// The unique identifier or null if the claim cannot be found. - public static string? GetAuthorizationId(this ClaimsPrincipal principal) - => principal.GetClaim(Claims.Private.AuthorizationId); - - /// - /// Gets the internal token identifier associated with the claims principal. - /// - /// The claims principal. - /// The unique identifier or null if the claim cannot be found. - public static string? GetTokenId(this ClaimsPrincipal principal) - => principal.GetClaim(Claims.Private.TokenId); - - /// - /// Gets the token type associated with the claims principal. - /// - /// The claims principal. - /// The token type or null if the claim cannot be found. - public static string? GetTokenType(this ClaimsPrincipal principal) - => principal.GetClaim(Claims.Private.TokenType); - - /// - /// Determines whether the claims principal contains the given audience. - /// - /// The claims principal. - /// The audience. - /// true if the principal contains the given audience. - public static bool HasAudience(this ClaimsPrincipal principal, string audience) - { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + /// + /// Gets the expiration date stored in the claims principal. + /// + /// The claims principal. + /// The expiration date or null if the claim cannot be found. + public static DateTimeOffset? GetExpirationDate(this ClaimsPrincipal principal) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } - if (string.IsNullOrEmpty(audience)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0186), nameof(audience)); - } + var claim = principal.FindFirst(Claims.Private.ExpirationDate); + if (claim is null) + { + return null; + } - return principal.HasClaim(Claims.Private.Audience, audience); + if (!DateTimeOffset.TryParseExact(claim.Value, "r", CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) + { + return null; + } + + return value; + } + + /// + /// Gets the audiences list stored in the claims principal. + /// + /// The claims principal. + /// The audiences list or an empty set if the claims cannot be found. + public static ImmutableArray GetAudiences(this ClaimsPrincipal principal) + => principal.GetClaims(Claims.Private.Audience); + + /// + /// Gets the presenters list stored in the claims principal. + /// + /// The claims principal. + /// The presenters list or an empty set if the claims cannot be found. + public static ImmutableArray GetPresenters(this ClaimsPrincipal principal) + => principal.GetClaims(Claims.Private.Presenter); + + /// + /// Gets the resources list stored in the claims principal. + /// + /// The claims principal. + /// The resources list or an empty set if the claims cannot be found. + public static ImmutableArray GetResources(this ClaimsPrincipal principal) + => principal.GetClaims(Claims.Private.Resource); + + /// + /// Gets the scopes list stored in the claims principal. + /// + /// The claims principal. + /// The scopes list or an empty set if the claim cannot be found. + public static ImmutableArray GetScopes(this ClaimsPrincipal principal) + => principal.GetClaims(Claims.Private.Scope); + + /// + /// Gets the access token lifetime associated with the claims principal. + /// + /// The claims principal. + /// The access token lifetime or null if the claim cannot be found. + public static TimeSpan? GetAccessTokenLifetime(this ClaimsPrincipal principal) + => GetLifetime(principal, Claims.Private.AccessTokenLifetime); + + /// + /// Gets the authorization code lifetime associated with the claims principal. + /// + /// The claims principal. + /// The authorization code lifetime or null if the claim cannot be found. + public static TimeSpan? GetAuthorizationCodeLifetime(this ClaimsPrincipal principal) + => GetLifetime(principal, Claims.Private.AuthorizationCodeLifetime); + + /// + /// Gets the device code lifetime associated with the claims principal. + /// + /// The claims principal. + /// The device code lifetime or null if the claim cannot be found. + public static TimeSpan? GetDeviceCodeLifetime(this ClaimsPrincipal principal) + => GetLifetime(principal, Claims.Private.DeviceCodeLifetime); + + /// + /// Gets the identity token lifetime associated with the claims principal. + /// + /// The claims principal. + /// The identity token lifetime or null if the claim cannot be found. + public static TimeSpan? GetIdentityTokenLifetime(this ClaimsPrincipal principal) + => GetLifetime(principal, Claims.Private.IdentityTokenLifetime); + + /// + /// Gets the refresh token lifetime associated with the claims principal. + /// + /// The claims principal. + /// The refresh token lifetime or null if the claim cannot be found. + public static TimeSpan? GetRefreshTokenLifetime(this ClaimsPrincipal principal) + => GetLifetime(principal, Claims.Private.RefreshTokenLifetime); + + /// + /// Gets the user code lifetime associated with the claims principal. + /// + /// The claims principal. + /// The user code lifetime or null if the claim cannot be found. + public static TimeSpan? GetUserCodeLifetime(this ClaimsPrincipal principal) + => GetLifetime(principal, Claims.Private.UserCodeLifetime); + + /// + /// Gets the internal authorization identifier associated with the claims principal. + /// + /// The claims principal. + /// The unique identifier or null if the claim cannot be found. + public static string? GetAuthorizationId(this ClaimsPrincipal principal) + => principal.GetClaim(Claims.Private.AuthorizationId); + + /// + /// Gets the internal token identifier associated with the claims principal. + /// + /// The claims principal. + /// The unique identifier or null if the claim cannot be found. + public static string? GetTokenId(this ClaimsPrincipal principal) + => principal.GetClaim(Claims.Private.TokenId); + + /// + /// Gets the token type associated with the claims principal. + /// + /// The claims principal. + /// The token type or null if the claim cannot be found. + public static string? GetTokenType(this ClaimsPrincipal principal) + => principal.GetClaim(Claims.Private.TokenType); + + /// + /// Determines whether the claims principal contains the given audience. + /// + /// The claims principal. + /// The audience. + /// true if the principal contains the given audience. + public static bool HasAudience(this ClaimsPrincipal principal, string audience) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Determines whether the claims principal contains the given presenter. - /// - /// The claims principal. - /// The presenter. - /// true if the principal contains the given presenter. - public static bool HasPresenter(this ClaimsPrincipal principal, string presenter) + if (string.IsNullOrEmpty(audience)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0186), nameof(audience)); + } - if (string.IsNullOrEmpty(presenter)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0187), nameof(presenter)); - } + return principal.HasClaim(Claims.Private.Audience, audience); + } - return principal.HasClaim(Claims.Private.Presenter, presenter); + /// + /// Determines whether the claims principal contains the given presenter. + /// + /// The claims principal. + /// The presenter. + /// true if the principal contains the given presenter. + public static bool HasPresenter(this ClaimsPrincipal principal, string presenter) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Determines whether the claims principal contains the given resource. - /// - /// The claims principal. - /// The resource. - /// true if the principal contains the given resource. - public static bool HasResource(this ClaimsPrincipal principal, string resource) + if (string.IsNullOrEmpty(presenter)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0187), nameof(presenter)); + } - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); - } + return principal.HasClaim(Claims.Private.Presenter, presenter); + } - return principal.HasClaim(Claims.Private.Resource, resource); + /// + /// Determines whether the claims principal contains the given resource. + /// + /// The claims principal. + /// The resource. + /// true if the principal contains the given resource. + public static bool HasResource(this ClaimsPrincipal principal, string resource) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); } - /// - /// Determines whether the claims principal contains the given scope. - /// - /// The claims principal. - /// The scope. - /// true if the principal contains the given scope. - public static bool HasScope(this ClaimsPrincipal principal, string scope) + if (string.IsNullOrEmpty(resource)) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); + } - if (string.IsNullOrEmpty(scope)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0180), nameof(scope)); - } + return principal.HasClaim(Claims.Private.Resource, resource); + } + + /// + /// Determines whether the claims principal contains the given scope. + /// + /// The claims principal. + /// The scope. + /// true if the principal contains the given scope. + public static bool HasScope(this ClaimsPrincipal principal, string scope) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } - return principal.HasClaim(Claims.Private.Scope, scope); + if (string.IsNullOrEmpty(scope)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0180), nameof(scope)); } - /// - /// Determines whether the token type associated with the claims principal matches the specified type. - /// - /// The claims principal. - /// The token type. - /// true if the token type matches the specified type. - public static bool HasTokenType(this ClaimsPrincipal principal, string type) + return principal.HasClaim(Claims.Private.Scope, scope); + } + + /// + /// Determines whether the token type associated with the claims principal matches the specified type. + /// + /// The claims principal. + /// The token type. + /// true if the token type matches the specified type. + public static bool HasTokenType(this ClaimsPrincipal principal, string type) + { + if (principal is null) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + throw new ArgumentNullException(nameof(principal)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0188), nameof(type)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0188), nameof(type)); + } - return string.Equals(principal.GetTokenType(), type, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Sets the creation date in the claims principal. - /// - /// The claims principal. - /// The creation date - /// The claims principal. - public static ClaimsPrincipal SetCreationDate(this ClaimsPrincipal principal, DateTimeOffset? date) - => principal.SetClaim(Claims.Private.CreationDate, date?.ToString("r", CultureInfo.InvariantCulture)); - - /// - /// Sets the expiration date in the claims principal. - /// - /// The claims principal. - /// The expiration date - /// The claims principal. - public static ClaimsPrincipal SetExpirationDate(this ClaimsPrincipal principal, DateTimeOffset? date) - => principal.SetClaim(Claims.Private.ExpirationDate, date?.ToString("r", CultureInfo.InvariantCulture)); - - /// - /// Sets the audiences list in the claims principal. - /// Note: this method automatically excludes duplicate audiences. - /// - /// The claims principal. - /// The audiences to store. - /// The claims principal. - public static ClaimsPrincipal SetAudiences(this ClaimsPrincipal principal, ImmutableArray audiences) - => principal.SetClaims(Claims.Private.Audience, audiences); - - /// - /// Sets the audiences list in the claims principal. - /// Note: this method automatically excludes duplicate audiences. - /// - /// The claims principal. - /// The audiences to store. - /// The claims principal. - public static ClaimsPrincipal SetAudiences(this ClaimsPrincipal principal, IEnumerable? audiences) - => principal.SetAudiences(audiences?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Sets the audiences list in the claims principal. - /// Note: this method automatically excludes duplicate audiences. - /// - /// The claims principal. - /// The audiences to store. - /// The claims principal. - public static ClaimsPrincipal SetAudiences(this ClaimsPrincipal principal, params string[]? audiences) - => principal.SetAudiences(audiences?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Sets the presenters list in the claims principal. - /// Note: this method automatically excludes duplicate presenters. - /// - /// The claims principal. - /// The presenters to store. - /// The claims principal. - public static ClaimsPrincipal SetPresenters(this ClaimsPrincipal principal, ImmutableArray presenters) - => principal.SetClaims(Claims.Private.Presenter, presenters); - - /// - /// Sets the presenters list in the claims principal. - /// Note: this method automatically excludes duplicate presenters. - /// - /// The claims principal. - /// The presenters to store. - /// The claims principal. - public static ClaimsPrincipal SetPresenters(this ClaimsPrincipal principal, IEnumerable? presenters) - => principal.SetPresenters(presenters?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Sets the presenters list in the claims principal. - /// Note: this method automatically excludes duplicate presenters. - /// - /// The claims principal. - /// The presenters to store. - /// The claims principal. - public static ClaimsPrincipal SetPresenters(this ClaimsPrincipal principal, params string[]? presenters) - => principal.SetPresenters(presenters?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Sets the resources list in the claims principal. - /// Note: this method automatically excludes duplicate resources. - /// - /// The claims principal. - /// The resources to store. - /// The claims principal. - public static ClaimsPrincipal SetResources(this ClaimsPrincipal principal, ImmutableArray resources) - => principal.SetClaims(Claims.Private.Resource, resources); - - /// - /// Sets the resources list in the claims principal. - /// Note: this method automatically excludes duplicate resources. - /// - /// The claims principal. - /// The resources to store. - /// The claims principal. - public static ClaimsPrincipal SetResources(this ClaimsPrincipal principal, IEnumerable? resources) - => principal.SetResources(resources?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Sets the resources list in the claims principal. - /// Note: this method automatically excludes duplicate resources. - /// - /// The claims principal. - /// The resources to store. - /// The claims principal. - public static ClaimsPrincipal SetResources(this ClaimsPrincipal principal, params string[]? resources) - => principal.SetResources(resources?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Sets the scopes list in the claims principal. - /// Note: this method automatically excludes duplicate scopes. - /// - /// The claims principal. - /// The scopes to store. - /// The claims principal. - public static ClaimsPrincipal SetScopes(this ClaimsPrincipal principal, ImmutableArray scopes) - => principal.SetClaims(Claims.Private.Scope, scopes); - - /// - /// Sets the scopes list in the claims principal. - /// Note: this method automatically excludes duplicate scopes. - /// - /// The claims principal. - /// The scopes to store. - /// The claims principal. - public static ClaimsPrincipal SetScopes(this ClaimsPrincipal principal, IEnumerable? scopes) - => principal.SetScopes(scopes?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Sets the scopes list in the claims principal. - /// Note: this method automatically excludes duplicate scopes. - /// - /// The claims principal. - /// The scopes to store. - /// The claims principal. - public static ClaimsPrincipal SetScopes(this ClaimsPrincipal principal, params string[]? scopes) - => principal.SetScopes(scopes?.ToImmutableArray() ?? ImmutableArray.Create()); - - /// - /// Sets the access token lifetime associated with the claims principal. - /// - /// The claims principal. - /// The access token lifetime to store. - /// The claims principal. - public static ClaimsPrincipal SetAccessTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) - => principal.SetClaim(Claims.Private.AccessTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); - - /// - /// Sets the authorization code lifetime associated with the claims principal. - /// - /// The claims principal. - /// The authorization code lifetime to store. - /// The claims principal. - public static ClaimsPrincipal SetAuthorizationCodeLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) - => principal.SetClaim(Claims.Private.AuthorizationCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); - - /// - /// Sets the device code lifetime associated with the claims principal. - /// - /// The claims principal. - /// The device code lifetime to store. - /// The claims principal. - public static ClaimsPrincipal SetDeviceCodeLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) - => principal.SetClaim(Claims.Private.DeviceCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); - - /// - /// Sets the identity token lifetime associated with the claims principal. - /// - /// The claims principal. - /// The identity token lifetime to store. - /// The claims principal. - public static ClaimsPrincipal SetIdentityTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) - => principal.SetClaim(Claims.Private.IdentityTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); - - /// - /// Sets the refresh token lifetime associated with the claims principal. - /// - /// The claims principal. - /// The refresh token lifetime to store. - /// The claims principal. - public static ClaimsPrincipal SetRefreshTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) - => principal.SetClaim(Claims.Private.RefreshTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); - - /// - /// Sets the user code lifetime associated with the claims principal. - /// - /// The claims principal. - /// The user code lifetime to store. - /// The claims principal. - public static ClaimsPrincipal SetUserCodeLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) - => principal.SetClaim(Claims.Private.UserCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); - - /// - /// Sets the internal authorization identifier associated with the claims principal. - /// - /// The claims principal. - /// The unique identifier to store. - /// The claims principal. - public static ClaimsPrincipal SetAuthorizationId(this ClaimsPrincipal principal, string? identifier) - => principal.SetClaim(Claims.Private.AuthorizationId, identifier); - - /// - /// Sets the internal token identifier associated with the claims principal. - /// - /// The claims principal. - /// The unique identifier to store. - /// The claims principal. - public static ClaimsPrincipal SetTokenId(this ClaimsPrincipal principal, string? identifier) - => principal.SetClaim(Claims.Private.TokenId, identifier); - - /// - /// Sets the token type associated with the claims principal. - /// - /// The claims principal. - /// The token type to store. - /// The claims principal. - public static ClaimsPrincipal SetTokenType(this ClaimsPrincipal principal, string? type) - => principal.SetClaim(Claims.Private.TokenType, type); - - private static ImmutableArray GetValues(string? source, char[] separators) - { - Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); - - if (string.IsNullOrEmpty(source)) - { - return ImmutableArray.Create(); - } + return string.Equals(principal.GetTokenType(), type, StringComparison.OrdinalIgnoreCase); + } - var builder = ImmutableArray.CreateBuilder(); + /// + /// Sets the creation date in the claims principal. + /// + /// The claims principal. + /// The creation date + /// The claims principal. + public static ClaimsPrincipal SetCreationDate(this ClaimsPrincipal principal, DateTimeOffset? date) + => principal.SetClaim(Claims.Private.CreationDate, date?.ToString("r", CultureInfo.InvariantCulture)); - foreach (var element in new StringTokenizer(source, separators)) - { - var segment = Trim(element, separators); - if (segment.Length == 0) - { - continue; - } + /// + /// Sets the expiration date in the claims principal. + /// + /// The claims principal. + /// The expiration date + /// The claims principal. + public static ClaimsPrincipal SetExpirationDate(this ClaimsPrincipal principal, DateTimeOffset? date) + => principal.SetClaim(Claims.Private.ExpirationDate, date?.ToString("r", CultureInfo.InvariantCulture)); - if (builder.Contains(segment.Value, StringComparer.Ordinal)) - { - continue; - } + /// + /// Sets the audiences list in the claims principal. + /// Note: this method automatically excludes duplicate audiences. + /// + /// The claims principal. + /// The audiences to store. + /// The claims principal. + public static ClaimsPrincipal SetAudiences(this ClaimsPrincipal principal, ImmutableArray audiences) + => principal.SetClaims(Claims.Private.Audience, audiences); - builder.Add(segment.Value); - } + /// + /// Sets the audiences list in the claims principal. + /// Note: this method automatically excludes duplicate audiences. + /// + /// The claims principal. + /// The audiences to store. + /// The claims principal. + public static ClaimsPrincipal SetAudiences(this ClaimsPrincipal principal, IEnumerable? audiences) + => principal.SetAudiences(audiences?.ToImmutableArray() ?? ImmutableArray.Create()); - return builder.ToImmutable(); - } + /// + /// Sets the audiences list in the claims principal. + /// Note: this method automatically excludes duplicate audiences. + /// + /// The claims principal. + /// The audiences to store. + /// The claims principal. + public static ClaimsPrincipal SetAudiences(this ClaimsPrincipal principal, params string[]? audiences) + => principal.SetAudiences(audiences?.ToImmutableArray() ?? ImmutableArray.Create()); - private static bool HasValue(string? source, string value, char[] separators) - { - Debug.Assert(!string.IsNullOrEmpty(value), SR.GetResourceString(SR.ID4002)); - Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + /// + /// Sets the presenters list in the claims principal. + /// Note: this method automatically excludes duplicate presenters. + /// + /// The claims principal. + /// The presenters to store. + /// The claims principal. + public static ClaimsPrincipal SetPresenters(this ClaimsPrincipal principal, ImmutableArray presenters) + => principal.SetClaims(Claims.Private.Presenter, presenters); - if (string.IsNullOrEmpty(source)) - { - return false; - } + /// + /// Sets the presenters list in the claims principal. + /// Note: this method automatically excludes duplicate presenters. + /// + /// The claims principal. + /// The presenters to store. + /// The claims principal. + public static ClaimsPrincipal SetPresenters(this ClaimsPrincipal principal, IEnumerable? presenters) + => principal.SetPresenters(presenters?.ToImmutableArray() ?? ImmutableArray.Create()); - foreach (var element in new StringTokenizer(source, separators)) - { - var segment = Trim(element, separators); - if (segment.Length == 0) - { - continue; - } + /// + /// Sets the presenters list in the claims principal. + /// Note: this method automatically excludes duplicate presenters. + /// + /// The claims principal. + /// The presenters to store. + /// The claims principal. + public static ClaimsPrincipal SetPresenters(this ClaimsPrincipal principal, params string[]? presenters) + => principal.SetPresenters(presenters?.ToImmutableArray() ?? ImmutableArray.Create()); - if (segment.Equals(value, StringComparison.Ordinal)) - { - return true; - } - } + /// + /// Sets the resources list in the claims principal. + /// Note: this method automatically excludes duplicate resources. + /// + /// The claims principal. + /// The resources to store. + /// The claims principal. + public static ClaimsPrincipal SetResources(this ClaimsPrincipal principal, ImmutableArray resources) + => principal.SetClaims(Claims.Private.Resource, resources); - return false; - } + /// + /// Sets the resources list in the claims principal. + /// Note: this method automatically excludes duplicate resources. + /// + /// The claims principal. + /// The resources to store. + /// The claims principal. + public static ClaimsPrincipal SetResources(this ClaimsPrincipal principal, IEnumerable? resources) + => principal.SetResources(resources?.ToImmutableArray() ?? ImmutableArray.Create()); - private static StringSegment TrimStart(StringSegment segment, char[] separators) - { - Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + /// + /// Sets the resources list in the claims principal. + /// Note: this method automatically excludes duplicate resources. + /// + /// The claims principal. + /// The resources to store. + /// The claims principal. + public static ClaimsPrincipal SetResources(this ClaimsPrincipal principal, params string[]? resources) + => principal.SetResources(resources?.ToImmutableArray() ?? ImmutableArray.Create()); - var index = segment.Offset; + /// + /// Sets the scopes list in the claims principal. + /// Note: this method automatically excludes duplicate scopes. + /// + /// The claims principal. + /// The scopes to store. + /// The claims principal. + public static ClaimsPrincipal SetScopes(this ClaimsPrincipal principal, ImmutableArray scopes) + => principal.SetClaims(Claims.Private.Scope, scopes); - while (index < segment.Offset + segment.Length) - { - if (!IsSeparator(segment.Buffer[index], separators)) - { - break; - } + /// + /// Sets the scopes list in the claims principal. + /// Note: this method automatically excludes duplicate scopes. + /// + /// The claims principal. + /// The scopes to store. + /// The claims principal. + public static ClaimsPrincipal SetScopes(this ClaimsPrincipal principal, IEnumerable? scopes) + => principal.SetScopes(scopes?.ToImmutableArray() ?? ImmutableArray.Create()); - index++; - } + /// + /// Sets the scopes list in the claims principal. + /// Note: this method automatically excludes duplicate scopes. + /// + /// The claims principal. + /// The scopes to store. + /// The claims principal. + public static ClaimsPrincipal SetScopes(this ClaimsPrincipal principal, params string[]? scopes) + => principal.SetScopes(scopes?.ToImmutableArray() ?? ImmutableArray.Create()); - return new StringSegment(segment.Buffer, index, segment.Offset + segment.Length - index); - } + /// + /// Sets the access token lifetime associated with the claims principal. + /// + /// The claims principal. + /// The access token lifetime to store. + /// The claims principal. + public static ClaimsPrincipal SetAccessTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) + => principal.SetClaim(Claims.Private.AccessTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + /// + /// Sets the authorization code lifetime associated with the claims principal. + /// + /// The claims principal. + /// The authorization code lifetime to store. + /// The claims principal. + public static ClaimsPrincipal SetAuthorizationCodeLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) + => principal.SetClaim(Claims.Private.AuthorizationCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + /// + /// Sets the device code lifetime associated with the claims principal. + /// + /// The claims principal. + /// The device code lifetime to store. + /// The claims principal. + public static ClaimsPrincipal SetDeviceCodeLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) + => principal.SetClaim(Claims.Private.DeviceCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); - private static StringSegment TrimEnd(StringSegment segment, char[] separators) + /// + /// Sets the identity token lifetime associated with the claims principal. + /// + /// The claims principal. + /// The identity token lifetime to store. + /// The claims principal. + public static ClaimsPrincipal SetIdentityTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) + => principal.SetClaim(Claims.Private.IdentityTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + /// + /// Sets the refresh token lifetime associated with the claims principal. + /// + /// The claims principal. + /// The refresh token lifetime to store. + /// The claims principal. + public static ClaimsPrincipal SetRefreshTokenLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) + => principal.SetClaim(Claims.Private.RefreshTokenLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + /// + /// Sets the user code lifetime associated with the claims principal. + /// + /// The claims principal. + /// The user code lifetime to store. + /// The claims principal. + public static ClaimsPrincipal SetUserCodeLifetime(this ClaimsPrincipal principal, TimeSpan? lifetime) + => principal.SetClaim(Claims.Private.UserCodeLifetime, lifetime?.TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + /// + /// Sets the internal authorization identifier associated with the claims principal. + /// + /// The claims principal. + /// The unique identifier to store. + /// The claims principal. + public static ClaimsPrincipal SetAuthorizationId(this ClaimsPrincipal principal, string? identifier) + => principal.SetClaim(Claims.Private.AuthorizationId, identifier); + + /// + /// Sets the internal token identifier associated with the claims principal. + /// + /// The claims principal. + /// The unique identifier to store. + /// The claims principal. + public static ClaimsPrincipal SetTokenId(this ClaimsPrincipal principal, string? identifier) + => principal.SetClaim(Claims.Private.TokenId, identifier); + + /// + /// Sets the token type associated with the claims principal. + /// + /// The claims principal. + /// The token type to store. + /// The claims principal. + public static ClaimsPrincipal SetTokenType(this ClaimsPrincipal principal, string? type) + => principal.SetClaim(Claims.Private.TokenType, type); + + private static ImmutableArray GetValues(string? source, char[] separators) + { + Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + + if (string.IsNullOrEmpty(source)) { - Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + return ImmutableArray.Create(); + } - var index = segment.Offset + segment.Length - 1; + var builder = ImmutableArray.CreateBuilder(); - while (index >= segment.Offset) + foreach (var element in new StringTokenizer(source, separators)) + { + var segment = Trim(element, separators); + if (segment.Length == 0) { - if (!IsSeparator(segment.Buffer[index], separators)) - { - break; - } + continue; + } - index--; + if (builder.Contains(segment.Value, StringComparer.Ordinal)) + { + continue; } - return new StringSegment(segment.Buffer, segment.Offset, index - segment.Offset + 1); + builder.Add(segment.Value); } - private static StringSegment Trim(StringSegment segment, char[] separators) - { - Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + return builder.ToImmutable(); + } - return TrimEnd(TrimStart(segment, separators), separators); - } + private static bool HasValue(string? source, string value, char[] separators) + { + Debug.Assert(!string.IsNullOrEmpty(value), SR.GetResourceString(SR.ID4002)); + Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); - private static bool IsSeparator(char character, char[] separators) + if (string.IsNullOrEmpty(source)) { - Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + return false; + } - for (var index = 0; index < separators!.Length; index++) + foreach (var element in new StringTokenizer(source, separators)) + { + var segment = Trim(element, separators); + if (segment.Length == 0) { - if (character == separators[index]) - { - return true; - } + continue; } - return false; + if (segment.Equals(value, StringComparison.Ordinal)) + { + return true; + } } - private static TimeSpan? GetLifetime(ClaimsPrincipal principal, string type) + return false; + } + + private static StringSegment TrimStart(StringSegment segment, char[] separators) + { + Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + + var index = segment.Offset; + + while (index < segment.Offset + segment.Length) { - if (principal is null) + if (!IsSeparator(segment.Buffer[index], separators)) { - throw new ArgumentNullException(nameof(principal)); + break; } - var value = principal.GetClaim(type); - if (string.IsNullOrEmpty(value)) + index++; + } + + return new StringSegment(segment.Buffer, index, segment.Offset + segment.Length - index); + } + + private static StringSegment TrimEnd(StringSegment segment, char[] separators) + { + Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + + var index = segment.Offset + segment.Length - 1; + + while (index >= segment.Offset) + { + if (!IsSeparator(segment.Buffer[index], separators)) { - return null; + break; } - if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double result)) + index--; + } + + return new StringSegment(segment.Buffer, segment.Offset, index - segment.Offset + 1); + } + + private static StringSegment Trim(StringSegment segment, char[] separators) + { + Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + + return TrimEnd(TrimStart(segment, separators), separators); + } + + private static bool IsSeparator(char character, char[] separators) + { + Debug.Assert(separators is not null && separators.Length != 0, SR.GetResourceString(SR.ID4001)); + + for (var index = 0; index < separators!.Length; index++) + { + if (character == separators[index]) { - return TimeSpan.FromSeconds(result); + return true; } + } + + return false; + } + private static TimeSpan? GetLifetime(ClaimsPrincipal principal, string type) + { + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + var value = principal.GetClaim(type); + if (string.IsNullOrEmpty(value)) + { return null; } + + if (double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out double result)) + { + return TimeSpan.FromSeconds(result); + } + + return null; } } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs index f8b7ad76..6ea18b19 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs @@ -17,397 +17,396 @@ using System.Text.Json.Serialization; using Microsoft.Extensions.Primitives; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Represents an abstract OpenIddict message. +/// +/// +/// Security notice: developers instantiating this type are responsible of ensuring that the +/// imported parameters are safe and won't cause the resulting message to grow abnormally, +/// which may result in an excessive memory consumption and a potential denial of service. +/// +[DebuggerDisplay("Parameters: {Parameters.Count}")] +[JsonConverter(typeof(OpenIddictConverter))] +public class OpenIddictMessage { /// - /// Represents an abstract OpenIddict message. + /// Initializes a new OpenIddict message. /// - /// - /// Security notice: developers instantiating this type are responsible of ensuring that the - /// imported parameters are safe and won't cause the resulting message to grow abnormally, - /// which may result in an excessive memory consumption and a potential denial of service. - /// - [DebuggerDisplay("Parameters: {Parameters.Count}")] - [JsonConverter(typeof(OpenIddictConverter))] - public class OpenIddictMessage + public OpenIddictMessage() { - /// - /// Initializes a new OpenIddict message. - /// - public OpenIddictMessage() + } + + /// + /// Initializes a new OpenIddict message. + /// + /// The message parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictMessage(JsonElement parameters) + { + if (parameters.ValueKind != JsonValueKind.Object) { + throw new ArgumentException(SR.GetResourceString(SR.ID0189), nameof(parameters)); } - /// - /// Initializes a new OpenIddict message. - /// - /// The message parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictMessage(JsonElement parameters) + foreach (var parameter in parameters.EnumerateObject()) { - if (parameters.ValueKind != JsonValueKind.Object) + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Name)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0189), nameof(parameters)); + continue; } - foreach (var parameter in parameters.EnumerateObject()) + // While generally discouraged, JSON objects can contain multiple properties with + // the same name. In this case, the last occurrence replaces the previous ones. + if (HasParameter(parameter.Name)) { - // Ignore parameters whose name is null or empty. - if (string.IsNullOrEmpty(parameter.Name)) - { - continue; - } + RemoveParameter(parameter.Name); + } - // While generally discouraged, JSON objects can contain multiple properties with - // the same name. In this case, the last occurrence replaces the previous ones. - if (HasParameter(parameter.Name)) - { - RemoveParameter(parameter.Name); - } + AddParameter(parameter.Name, parameter.Value); + } + } - AddParameter(parameter.Name, parameter.Value); - } + /// + /// Initializes a new OpenIddict message. + /// + /// The message parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictMessage(IEnumerable> parameters) + { + if (parameters is null) + { + throw new ArgumentNullException(nameof(parameters)); } - /// - /// Initializes a new OpenIddict message. - /// - /// The message parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictMessage(IEnumerable> parameters) + foreach (var parameter in parameters) { - if (parameters is null) + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Key)) { - throw new ArgumentNullException(nameof(parameters)); + continue; } - foreach (var parameter in parameters) - { - // Ignore parameters whose name is null or empty. - if (string.IsNullOrEmpty(parameter.Key)) - { - continue; - } + AddParameter(parameter.Key, parameter.Value); + } + } - AddParameter(parameter.Key, parameter.Value); - } + /// + /// Initializes a new OpenIddict message. + /// + /// The message parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictMessage(IEnumerable> parameters) + { + if (parameters is null) + { + throw new ArgumentNullException(nameof(parameters)); } - /// - /// Initializes a new OpenIddict message. - /// - /// The message parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictMessage(IEnumerable> parameters) + foreach (var parameter in parameters.GroupBy(parameter => parameter.Key)) { - if (parameters is null) + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Key)) { - throw new ArgumentNullException(nameof(parameters)); + continue; } - foreach (var parameter in parameters.GroupBy(parameter => parameter.Key)) + var values = parameter.Select(parameter => parameter.Value).ToArray(); + + // Note: the core OAuth 2.0 specification requires that request parameters + // not be present more than once but derived specifications like the + // token exchange specification deliberately allow specifying multiple + // parameters with the same name to represent a multi-valued parameter. + AddParameter(parameter.Key, values.Length switch { - // Ignore parameters whose name is null or empty. - if (string.IsNullOrEmpty(parameter.Key)) - { - continue; - } - - var values = parameter.Select(parameter => parameter.Value).ToArray(); - - // Note: the core OAuth 2.0 specification requires that request parameters - // not be present more than once but derived specifications like the - // token exchange specification deliberately allow specifying multiple - // parameters with the same name to represent a multi-valued parameter. - AddParameter(parameter.Key, values.Length switch - { - 0 => default, - 1 => values[0], - _ => values - }); - } + 0 => default, + 1 => values[0], + _ => values + }); } + } - /// - /// Initializes a new OpenIddict message. - /// - /// The message parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictMessage(IEnumerable> parameters) + /// + /// Initializes a new OpenIddict message. + /// + /// The message parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictMessage(IEnumerable> parameters) + { + if (parameters is null) { - if (parameters is null) - { - throw new ArgumentNullException(nameof(parameters)); - } - - foreach (var parameter in parameters) - { - // Ignore parameters whose name is null or empty. - if (string.IsNullOrEmpty(parameter.Key)) - { - continue; - } - - // Note: the core OAuth 2.0 specification requires that request parameters - // not be present more than once but derived specifications like the - // token exchange specification deliberately allow specifying multiple - // parameters with the same name to represent a multi-valued parameter. - AddParameter(parameter.Key, parameter.Value?.Length switch - { - null or 0 => default, - 1 => parameter.Value[0], - _ => parameter.Value - }); - } + throw new ArgumentNullException(nameof(parameters)); } - /// - /// Initializes a new OpenIddict message. - /// - /// The message parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictMessage(IEnumerable> parameters) + foreach (var parameter in parameters) { - if (parameters is null) + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Key)) { - throw new ArgumentNullException(nameof(parameters)); + continue; } - foreach (var parameter in parameters) + // Note: the core OAuth 2.0 specification requires that request parameters + // not be present more than once but derived specifications like the + // token exchange specification deliberately allow specifying multiple + // parameters with the same name to represent a multi-valued parameter. + AddParameter(parameter.Key, parameter.Value?.Length switch { - // Ignore parameters whose name is null or empty. - if (string.IsNullOrEmpty(parameter.Key)) - { - continue; - } - - // Note: the core OAuth 2.0 specification requires that request parameters - // not be present more than once but derived specifications like the - // token exchange specification deliberately allow specifying multiple - // parameters with the same name to represent a multi-valued parameter. - AddParameter(parameter.Key, parameter.Value.Count switch - { - 0 => default, - 1 => parameter.Value[0], - _ => parameter.Value.ToArray() - }); - } + null or 0 => default, + 1 => parameter.Value[0], + _ => parameter.Value + }); } + } - /// - /// Gets or sets a parameter. - /// - /// The parameter name. - /// The parameter value. - public OpenIddictParameter? this[string name] + /// + /// Initializes a new OpenIddict message. + /// + /// The message parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictMessage(IEnumerable> parameters) + { + if (parameters is null) { - get => GetParameter(name); - set => SetParameter(name, value); + throw new ArgumentNullException(nameof(parameters)); } - /// - /// Gets the number of parameters contained in the current message. - /// - public int Count => Parameters.Count; - - /// - /// Gets the dictionary containing the parameters. - /// - protected Dictionary Parameters { get; } - = new Dictionary(StringComparer.Ordinal); - - /// - /// Adds a parameter. Note: an exception is thrown if a parameter with the same name was already added. - /// - /// The parameter name. - /// The parameter value. - /// The current instance, which allows chaining calls. - public OpenIddictMessage AddParameter(string name, OpenIddictParameter value) + foreach (var parameter in parameters) { - if (string.IsNullOrEmpty(name)) + // Ignore parameters whose name is null or empty. + if (string.IsNullOrEmpty(parameter.Key)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); + continue; } - if (Parameters.ContainsKey(name)) + // Note: the core OAuth 2.0 specification requires that request parameters + // not be present more than once but derived specifications like the + // token exchange specification deliberately allow specifying multiple + // parameters with the same name to represent a multi-valued parameter. + AddParameter(parameter.Key, parameter.Value.Count switch { - throw new ArgumentException(SR.GetResourceString(SR.ID0191), nameof(name)); - } + 0 => default, + 1 => parameter.Value[0], + _ => parameter.Value.ToArray() + }); + } + } - Parameters.Add(name, value); + /// + /// Gets or sets a parameter. + /// + /// The parameter name. + /// The parameter value. + public OpenIddictParameter? this[string name] + { + get => GetParameter(name); + set => SetParameter(name, value); + } - return this; + /// + /// Gets the number of parameters contained in the current message. + /// + public int Count => Parameters.Count; + + /// + /// Gets the dictionary containing the parameters. + /// + protected Dictionary Parameters { get; } + = new Dictionary(StringComparer.Ordinal); + + /// + /// Adds a parameter. Note: an exception is thrown if a parameter with the same name was already added. + /// + /// The parameter name. + /// The parameter value. + /// The current instance, which allows chaining calls. + public OpenIddictMessage AddParameter(string name, OpenIddictParameter value) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); } - /// - /// Gets the value corresponding to a given parameter. - /// - /// The parameter name. - /// The parameter value, or null if it cannot be found. - public OpenIddictParameter? GetParameter(string name) + if (Parameters.ContainsKey(name)) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0191), nameof(name)); + } - if (Parameters.TryGetValue(name, out OpenIddictParameter value)) - { - return value; - } + Parameters.Add(name, value); - return null; - } + return this; + } - /// - /// Gets all the parameters associated with this instance. - /// - /// The parameters associated with this instance. - public IReadOnlyDictionary GetParameters() - => new ReadOnlyDictionary(Parameters); - - /// - /// Determines whether the current message contains the specified parameter. - /// - /// The parameter name. - /// true if the parameter is present, false otherwise. - public bool HasParameter(string name) + /// + /// Gets the value corresponding to a given parameter. + /// + /// The parameter name. + /// The parameter value, or null if it cannot be found. + public OpenIddictParameter? GetParameter(string name) + { + if (string.IsNullOrEmpty(name)) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); - } - - return Parameters.ContainsKey(name); + throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); } - /// - /// Removes a parameter. - /// - /// The parameter name. - /// The current instance, which allows chaining calls. - public OpenIddictMessage RemoveParameter(string name) + if (Parameters.TryGetValue(name, out OpenIddictParameter value)) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); - } + return value; + } - Parameters.Remove(name); + return null; + } - return this; + /// + /// Gets all the parameters associated with this instance. + /// + /// The parameters associated with this instance. + public IReadOnlyDictionary GetParameters() + => new ReadOnlyDictionary(Parameters); + + /// + /// Determines whether the current message contains the specified parameter. + /// + /// The parameter name. + /// true if the parameter is present, false otherwise. + public bool HasParameter(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); } - /// - /// Adds, replaces or removes a parameter. - /// Note: this method automatically removes empty parameters. - /// - /// The parameter name. - /// The parameter value. - /// The current instance, which allows chaining calls. - public OpenIddictMessage SetParameter(string name, OpenIddictParameter? value) + return Parameters.ContainsKey(name); + } + + /// + /// Removes a parameter. + /// + /// The parameter name. + /// The current instance, which allows chaining calls. + public OpenIddictMessage RemoveParameter(string name) + { + if (string.IsNullOrEmpty(name)) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); + } - // If the parameter value is null or empty, remove the corresponding entry from the collection. - if (value is null || OpenIddictParameter.IsNullOrEmpty(value.GetValueOrDefault())) - { - Parameters.Remove(name); - } + Parameters.Remove(name); - else - { - Parameters[name] = value.GetValueOrDefault(); - } + return this; + } - return this; + /// + /// Adds, replaces or removes a parameter. + /// Note: this method automatically removes empty parameters. + /// + /// The parameter name. + /// The parameter value. + /// The current instance, which allows chaining calls. + public OpenIddictMessage SetParameter(string name, OpenIddictParameter? value) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); } - /// - /// Tries to get the value corresponding to a given parameter. - /// - /// The parameter name. - /// The parameter value. - /// true if the parameter could be found, false otherwise. - public bool TryGetParameter(string name, out OpenIddictParameter value) + // If the parameter value is null or empty, remove the corresponding entry from the collection. + if (value is null || OpenIddictParameter.IsNullOrEmpty(value.GetValueOrDefault())) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); - } - - return Parameters.TryGetValue(name, out value); + Parameters.Remove(name); } - /// - /// Returns a representation of the current instance that can be used in logs. - /// Note: sensitive parameters like client secrets are automatically removed for security reasons. - /// - /// The indented JSON representation corresponding to this message. - public override string ToString() + else { - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = true - }); + Parameters[name] = value.GetValueOrDefault(); + } - writer.WriteStartObject(); + return this; + } - foreach (var parameter in Parameters) - { - writer.WritePropertyName(parameter.Key); - - // Remove sensitive parameters from the generated payload. - switch (parameter.Key) - { - case OpenIddictConstants.Parameters.AccessToken: - case OpenIddictConstants.Parameters.Assertion: - case OpenIddictConstants.Parameters.ClientAssertion: - case OpenIddictConstants.Parameters.ClientSecret: - case OpenIddictConstants.Parameters.Code: - case OpenIddictConstants.Parameters.IdToken: - case OpenIddictConstants.Parameters.IdTokenHint: - case OpenIddictConstants.Parameters.Password: - case OpenIddictConstants.Parameters.RefreshToken: - case OpenIddictConstants.Parameters.Token: - writer.WriteStringValue("[redacted]"); - continue; - } - - parameter.Value.WriteTo(writer); - } + /// + /// Tries to get the value corresponding to a given parameter. + /// + /// The parameter name. + /// The parameter value. + /// true if the parameter could be found, false otherwise. + public bool TryGetParameter(string name, out OpenIddictParameter value) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0190), nameof(name)); + } - writer.WriteEndObject(); - writer.Flush(); + return Parameters.TryGetValue(name, out value); + } - return Encoding.UTF8.GetString(stream.ToArray()); - } + /// + /// Returns a representation of the current instance that can be used in logs. + /// Note: sensitive parameters like client secrets are automatically removed for security reasons. + /// + /// The indented JSON representation corresponding to this message. + public override string ToString() + { + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = true + }); - /// - /// Writes the message to the specified JSON writer. - /// - /// The UTF-8 JSON writer. - public void WriteTo(Utf8JsonWriter writer) + writer.WriteStartObject(); + + foreach (var parameter in Parameters) { - if (writer is null) + writer.WritePropertyName(parameter.Key); + + // Remove sensitive parameters from the generated payload. + switch (parameter.Key) { - throw new ArgumentNullException(nameof(writer)); + case OpenIddictConstants.Parameters.AccessToken: + case OpenIddictConstants.Parameters.Assertion: + case OpenIddictConstants.Parameters.ClientAssertion: + case OpenIddictConstants.Parameters.ClientSecret: + case OpenIddictConstants.Parameters.Code: + case OpenIddictConstants.Parameters.IdToken: + case OpenIddictConstants.Parameters.IdTokenHint: + case OpenIddictConstants.Parameters.Password: + case OpenIddictConstants.Parameters.RefreshToken: + case OpenIddictConstants.Parameters.Token: + writer.WriteStringValue("[redacted]"); + continue; } - writer.WriteStartObject(); + parameter.Value.WriteTo(writer); + } - foreach (var parameter in Parameters) - { - writer.WritePropertyName(parameter.Key); - parameter.Value.WriteTo(writer); - } + writer.WriteEndObject(); + writer.Flush(); + + return Encoding.UTF8.GetString(stream.ToArray()); + } - writer.WriteEndObject(); + /// + /// Writes the message to the specified JSON writer. + /// + /// The UTF-8 JSON writer. + public void WriteTo(Utf8JsonWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteStartObject(); + + foreach (var parameter in Parameters) + { + writer.WritePropertyName(parameter.Key); + parameter.Value.WriteTo(writer); } + + writer.WriteEndObject(); } } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs index fcdeabc1..038c2815 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs @@ -16,512 +16,645 @@ using System.Text.Encodings.Web; using System.Text.Json; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Represents an OpenIddict parameter value, that can be either a primitive value, +/// an array of strings or a complex JSON representation containing child nodes. +/// +public readonly struct OpenIddictParameter : IEquatable { /// - /// Represents an OpenIddict parameter value, that can be either a primitive value, - /// an array of strings or a complex JSON representation containing child nodes. + /// Initializes a new parameter using the specified value. + /// + /// The parameter value. + public OpenIddictParameter(bool value) => Value = value; + + /// + /// Initializes a new parameter using the specified value. /// - public readonly struct OpenIddictParameter : IEquatable + /// The parameter value. + public OpenIddictParameter(bool? value) => Value = value; + + /// + /// Initializes a new parameter using the specified value. + /// + /// The parameter value. + public OpenIddictParameter(JsonElement value) => Value = value; + + /// + /// Initializes a new parameter using the specified value. + /// + /// The parameter value. + public OpenIddictParameter(long value) => Value = value; + + /// + /// Initializes a new parameter using the specified value. + /// + /// The parameter value. + public OpenIddictParameter(long? value) => Value = value; + + /// + /// Initializes a new parameter using the specified value. + /// + /// The parameter value. + public OpenIddictParameter(string? value) => Value = value; + + /// + /// Initializes a new parameter using the specified value. + /// + /// The parameter value. + public OpenIddictParameter(string?[]? value) => Value = value; + + /// + /// Gets the child item corresponding to the specified index. + /// + /// The index of the child item. + /// An instance containing the item value. + public OpenIddictParameter? this[int index] => GetUnnamedParameter(index); + + /// + /// Gets the child item corresponding to the specified name. + /// + /// The name of the child item. + /// An instance containing the item value. + public OpenIddictParameter? this[string name] => GetNamedParameter(name); + + /// + /// Gets the number of unnamed child items contained in the current parameter or + /// 0 if the parameter doesn't represent an array of strings or a JSON array. + /// + public int Count => Value switch { - /// - /// Initializes a new parameter using the specified value. - /// - /// The parameter value. - public OpenIddictParameter(bool value) => Value = value; - - /// - /// Initializes a new parameter using the specified value. - /// - /// The parameter value. - public OpenIddictParameter(bool? value) => Value = value; - - /// - /// Initializes a new parameter using the specified value. - /// - /// The parameter value. - public OpenIddictParameter(JsonElement value) => Value = value; - - /// - /// Initializes a new parameter using the specified value. - /// - /// The parameter value. - public OpenIddictParameter(long value) => Value = value; - - /// - /// Initializes a new parameter using the specified value. - /// - /// The parameter value. - public OpenIddictParameter(long? value) => Value = value; - - /// - /// Initializes a new parameter using the specified value. - /// - /// The parameter value. - public OpenIddictParameter(string? value) => Value = value; - - /// - /// Initializes a new parameter using the specified value. - /// - /// The parameter value. - public OpenIddictParameter(string?[]? value) => Value = value; - - /// - /// Gets the child item corresponding to the specified index. - /// - /// The index of the child item. - /// An instance containing the item value. - public OpenIddictParameter? this[int index] => GetUnnamedParameter(index); - - /// - /// Gets the child item corresponding to the specified name. - /// - /// The name of the child item. - /// An instance containing the item value. - public OpenIddictParameter? this[string name] => GetNamedParameter(name); - - /// - /// Gets the number of unnamed child items contained in the current parameter or - /// 0 if the parameter doesn't represent an array of strings or a JSON array. - /// - public int Count => Value switch - { - // If the parameter is a primitive array of strings, return its length. - string?[] value => value.Length, + // If the parameter is a primitive array of strings, return its length. + string?[] value => value.Length, - // If the parameter is a JSON array, return its length. - JsonElement { ValueKind: JsonValueKind.Array } value => value.GetArrayLength(), + // If the parameter is a JSON array, return its length. + JsonElement { ValueKind: JsonValueKind.Array } value => value.GetArrayLength(), - // Otherwise, return 0. - _ => 0 - }; + // Otherwise, return 0. + _ => 0 + }; + + /// + /// Gets the associated value, that can be either a primitive CLR type + /// (e.g bool, string, long), an array of strings or a complex JSON object. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public object? Value { get; } - /// - /// Gets the associated value, that can be either a primitive CLR type - /// (e.g bool, string, long), an array of strings or a complex JSON object. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public object? Value { get; } - - /// - /// Determines whether the current - /// instance is equal to the specified . - /// - /// The other object to which to compare this instance. - /// true if the two instances are equal, false otherwise. - public bool Equals(OpenIddictParameter other) + /// + /// Determines whether the current + /// instance is equal to the specified . + /// + /// The other object to which to compare this instance. + /// true if the two instances are equal, false otherwise. + public bool Equals(OpenIddictParameter other) + { + return (left: Value, right: other.Value) switch { - return (left: Value, right: other.Value) switch - { - // If the two parameters reference the same instance, return true. - // Note: true will also be returned if the two parameters are null. - var (left, right) when ReferenceEquals(left, right) => true, + // If the two parameters reference the same instance, return true. + // Note: true will also be returned if the two parameters are null. + var (left, right) when ReferenceEquals(left, right) => true, - // If one of the two parameters is null, return false. - (null, _) or (_, null) => false, + // If one of the two parameters is null, return false. + (null, _) or (_, null) => false, - // If the two parameters are string arrays, use SequenceEqual(). - (string?[] left, string?[] right) => left.SequenceEqual(right), + // If the two parameters are string arrays, use SequenceEqual(). + (string?[] left, string?[] right) => left.SequenceEqual(right), - // If the two parameters are JsonElement instances, use the custom comparer. - (JsonElement left, JsonElement right) => Equals(left, right), + // If the two parameters are JsonElement instances, use the custom comparer. + (JsonElement left, JsonElement right) => Equals(left, right), - // When one of the parameters is a bool, compare them as booleans. - (JsonElement { ValueKind: JsonValueKind.True }, bool right) => right, - (JsonElement { ValueKind: JsonValueKind.False }, bool right) => !right, + // When one of the parameters is a bool, compare them as booleans. + (JsonElement { ValueKind: JsonValueKind.True }, bool right) => right, + (JsonElement { ValueKind: JsonValueKind.False }, bool right) => !right, - (bool left, JsonElement { ValueKind: JsonValueKind.True }) => left, - (bool left, JsonElement { ValueKind: JsonValueKind.False }) => !left, + (bool left, JsonElement { ValueKind: JsonValueKind.True }) => left, + (bool left, JsonElement { ValueKind: JsonValueKind.False }) => !left, - // When one of the parameters is a number, compare them as integers. - (JsonElement { ValueKind: JsonValueKind.Number } left, long right) - => right == left.GetInt64(), + // When one of the parameters is a number, compare them as integers. + (JsonElement { ValueKind: JsonValueKind.Number } left, long right) + => right == left.GetInt64(), - (long left, JsonElement { ValueKind: JsonValueKind.Number } right) - => left == right.GetInt64(), + (long left, JsonElement { ValueKind: JsonValueKind.Number } right) + => left == right.GetInt64(), - // When one of the parameters is a string, compare them as texts. - (JsonElement { ValueKind: JsonValueKind.String } left, string right) - => string.Equals(left.GetString(), right, StringComparison.Ordinal), + // When one of the parameters is a string, compare them as texts. + (JsonElement { ValueKind: JsonValueKind.String } left, string right) + => string.Equals(left.GetString(), right, StringComparison.Ordinal), - (string left, JsonElement { ValueKind: JsonValueKind.String } right) - => string.Equals(left, right.GetString(), StringComparison.Ordinal), + (string left, JsonElement { ValueKind: JsonValueKind.String } right) + => string.Equals(left, right.GetString(), StringComparison.Ordinal), - // Otherwise, use direct CLR comparison. - var (left, right) => left.Equals(right) - }; + // Otherwise, use direct CLR comparison. + var (left, right) => left.Equals(right) + }; - static bool Equals(JsonElement left, JsonElement right) + static bool Equals(JsonElement left, JsonElement right) + { + switch (left.ValueKind) { - switch (left.ValueKind) - { - case JsonValueKind.Undefined: - return right.ValueKind == JsonValueKind.Undefined; + case JsonValueKind.Undefined: + return right.ValueKind == JsonValueKind.Undefined; - case JsonValueKind.Null: - return right.ValueKind == JsonValueKind.Null; + case JsonValueKind.Null: + return right.ValueKind == JsonValueKind.Null; - case JsonValueKind.False: - return right.ValueKind == JsonValueKind.False; + case JsonValueKind.False: + return right.ValueKind == JsonValueKind.False; - case JsonValueKind.True: - return right.ValueKind == JsonValueKind.True; + case JsonValueKind.True: + return right.ValueKind == JsonValueKind.True; - case JsonValueKind.Number when right.ValueKind == JsonValueKind.Number: - return left.GetInt64() == right.GetInt64(); + case JsonValueKind.Number when right.ValueKind == JsonValueKind.Number: + return left.GetInt64() == right.GetInt64(); - case JsonValueKind.String when right.ValueKind == JsonValueKind.String: - return string.Equals(left.GetString(), right.GetString(), StringComparison.Ordinal); + case JsonValueKind.String when right.ValueKind == JsonValueKind.String: + return string.Equals(left.GetString(), right.GetString(), StringComparison.Ordinal); - case JsonValueKind.Array when right.ValueKind == JsonValueKind.Array: - if (left.GetArrayLength() != right.GetArrayLength()) - { - return false; - } + case JsonValueKind.Array when right.ValueKind == JsonValueKind.Array: + if (left.GetArrayLength() != right.GetArrayLength()) + { + return false; + } - using (var enumerator = left.EnumerateArray()) + using (var enumerator = left.EnumerateArray()) + { + for (var index = 0; enumerator.MoveNext(); index++) { - for (var index = 0; enumerator.MoveNext(); index++) + if (!Equals(left[index], right[index])) { - if (!Equals(left[index], right[index])) - { - return false; - } + return false; } } + } - return true; + return true; - case JsonValueKind.Object when right.ValueKind == JsonValueKind.Object: - foreach (var property in left.EnumerateObject()) + case JsonValueKind.Object when right.ValueKind == JsonValueKind.Object: + foreach (var property in left.EnumerateObject()) + { + if (!right.TryGetProperty(property.Name, out JsonElement element) || + property.Value.ValueKind != element.ValueKind) { - if (!right.TryGetProperty(property.Name, out JsonElement element) || - property.Value.ValueKind != element.ValueKind) - { - return false; - } + return false; + } - if (!Equals(property.Value, element)) - { - return false; - } + if (!Equals(property.Value, element)) + { + return false; } + } - return true; + return true; - default: return false; - } + default: return false; } } + } - /// - /// Determines whether the current - /// instance is equal to the specified . - /// - /// The other object to which to compare this instance. - /// true if the two instances are equal, false otherwise. - public override bool Equals(object? obj) => obj is OpenIddictParameter parameter && Equals(parameter); - - /// - /// Returns the hash code of the current instance. - /// - /// The hash code for the current instance. - public override int GetHashCode() + /// + /// Determines whether the current + /// instance is equal to the specified . + /// + /// The other object to which to compare this instance. + /// true if the two instances are equal, false otherwise. + public override bool Equals(object? obj) => obj is OpenIddictParameter parameter && Equals(parameter); + + /// + /// Returns the hash code of the current instance. + /// + /// The hash code for the current instance. + public override int GetHashCode() + { + return Value switch { - return Value switch - { - // When the parameter value is null, return 0. - null => 0, + // When the parameter value is null, return 0. + null => 0, - // When the parameter is a JsonElement, compute its hash code. - JsonElement value => GetHashCode(value), + // When the parameter is a JsonElement, compute its hash code. + JsonElement value => GetHashCode(value), - // Otherwise, use the default hash code method. - var value => value.GetHashCode() - }; + // Otherwise, use the default hash code method. + var value => value.GetHashCode() + }; - static int GetHashCode(JsonElement value) + static int GetHashCode(JsonElement value) + { + switch (value.ValueKind) { - switch (value.ValueKind) - { - case JsonValueKind.Undefined: - case JsonValueKind.Null: - return 0; - - case JsonValueKind.False: - return false.GetHashCode(); + case JsonValueKind.Undefined: + case JsonValueKind.Null: + return 0; - case JsonValueKind.True: - return true.GetHashCode(); + case JsonValueKind.False: + return false.GetHashCode(); - case JsonValueKind.Number: - return value.GetInt64().GetHashCode(); + case JsonValueKind.True: + return true.GetHashCode(); - case JsonValueKind.String: - return value.GetString()!.GetHashCode(); + case JsonValueKind.Number: + return value.GetInt64().GetHashCode(); - case JsonValueKind.Array: - { - var hash = new HashCode(); + case JsonValueKind.String: + return value.GetString()!.GetHashCode(); - foreach (var element in value.EnumerateArray()) - { - hash.Add(GetHashCode(element)); - } + case JsonValueKind.Array: + { + var hash = new HashCode(); - return hash.ToHashCode(); + foreach (var element in value.EnumerateArray()) + { + hash.Add(GetHashCode(element)); } - case JsonValueKind.Object: - { - var hash = new HashCode(); + return hash.ToHashCode(); + } - foreach (var property in value.EnumerateObject()) - { - hash.Add(property.Name); - hash.Add(GetHashCode(property.Value)); - } + case JsonValueKind.Object: + { + var hash = new HashCode(); - return hash.ToHashCode(); + foreach (var property in value.EnumerateObject()) + { + hash.Add(property.Name); + hash.Add(GetHashCode(property.Value)); } - default: return 0; + return hash.ToHashCode(); } + + default: return 0; } } + } + + /// + /// Gets the child item corresponding to the specified name. + /// + /// The name of the child item. + /// An instance containing the item value. + public OpenIddictParameter? GetNamedParameter(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0192), nameof(name)); + } - /// - /// Gets the child item corresponding to the specified name. - /// - /// The name of the child item. - /// An instance containing the item value. - public OpenIddictParameter? GetNamedParameter(string name) + if (Value is JsonElement { ValueKind: JsonValueKind.Object } element) { - if (string.IsNullOrEmpty(name)) + if (element.TryGetProperty(name, out JsonElement value)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0192), nameof(name)); + return new OpenIddictParameter(value); } - if (Value is JsonElement { ValueKind: JsonValueKind.Object } element) - { - if (element.TryGetProperty(name, out JsonElement value)) - { - return new OpenIddictParameter(value); - } + // If the item doesn't exist, return a null parameter. + return null; + } + + return null; + } + + /// + /// Gets the child item corresponding to the specified index. + /// + /// The index of the child item. + /// An instance containing the item value. + public OpenIddictParameter? GetUnnamedParameter(int index) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.GetResourceString(SR.ID0193)); + } - // If the item doesn't exist, return a null parameter. + if (Value is string?[] array) + { + // If the specified index goes beyond the + // number of items in the array, return null. + if (index >= array.Length) + { return null; } - return null; + return new OpenIddictParameter(array[index]); } - /// - /// Gets the child item corresponding to the specified index. - /// - /// The index of the child item. - /// An instance containing the item value. - public OpenIddictParameter? GetUnnamedParameter(int index) + if (Value is JsonElement { ValueKind: JsonValueKind.Array } element) { - if (index < 0) + // If the specified index goes beyond the + // number of items in the array, return null. + if (index >= element.GetArrayLength()) { - throw new ArgumentOutOfRangeException(nameof(index), SR.GetResourceString(SR.ID0193)); + return null; } - if (Value is string?[] array) - { - // If the specified index goes beyond the - // number of items in the array, return null. - if (index >= array.Length) - { - return null; - } + return new OpenIddictParameter(element[index]); + } - return new OpenIddictParameter(array[index]); - } + // If the value is not a JSON array, return null. + return null; + } - if (Value is JsonElement { ValueKind: JsonValueKind.Array } element) - { - // If the specified index goes beyond the - // number of items in the array, return null. - if (index >= element.GetArrayLength()) - { - return null; - } + /// + /// Gets the named child items associated with the current parameter, if it represents a JSON object. + /// Note: if the JSON object contains multiple parameters with the same name, only the last occurrence is returned. + /// + /// A dictionary of all the parameters associated with the current instance. + public IReadOnlyDictionary GetNamedParameters() + { + if (Value is JsonElement { ValueKind: JsonValueKind.Object } element) + { + var parameters = new Dictionary(StringComparer.Ordinal); - return new OpenIddictParameter(element[index]); + foreach (var property in element.EnumerateObject()) + { + parameters[property.Name] = property.Value; } - // If the value is not a JSON array, return null. - return null; + return parameters; } - /// - /// Gets the named child items associated with the current parameter, if it represents a JSON object. - /// Note: if the JSON object contains multiple parameters with the same name, only the last occurrence is returned. - /// - /// A dictionary of all the parameters associated with the current instance. - public IReadOnlyDictionary GetNamedParameters() - { - if (Value is JsonElement { ValueKind: JsonValueKind.Object } element) - { - var parameters = new Dictionary(StringComparer.Ordinal); + return ImmutableDictionary.Create(StringComparer.Ordinal); + } - foreach (var property in element.EnumerateObject()) - { - parameters[property.Name] = property.Value; - } + /// + /// Gets the unnamed child items associated with the current parameter, + /// if it represents an array of strings or a JSON array. + /// + /// An enumeration of all the unnamed parameters associated with the current instance. + public IReadOnlyList GetUnnamedParameters() + { + if (Value is string?[] array) + { + var parameters = new List(); - return parameters; + for (var index = 0; index < array.Length; index++) + { + parameters.Add(array[index]); } - return ImmutableDictionary.Create(StringComparer.Ordinal); + return parameters; } - /// - /// Gets the unnamed child items associated with the current parameter, - /// if it represents an array of strings or a JSON array. - /// - /// An enumeration of all the unnamed parameters associated with the current instance. - public IReadOnlyList GetUnnamedParameters() + else if (Value is JsonElement { ValueKind: JsonValueKind.Array } element) { - if (Value is string?[] array) + var parameters = new List(); + + foreach (var value in element.EnumerateArray()) { - var parameters = new List(); + parameters.Add(value); + } - for (var index = 0; index < array.Length; index++) - { - parameters.Add(array[index]); - } + return parameters; + } - return parameters; - } + return ImmutableList.Create(); + } - else if (Value is JsonElement { ValueKind: JsonValueKind.Array } element) - { - var parameters = new List(); + /// + /// Returns the representation of the current instance. + /// + /// The representation associated with the parameter value. + public override string? ToString() => Value switch + { + null => string.Empty, - foreach (var value in element.EnumerateArray()) - { - parameters.Add(value); - } + bool value => value ? bool.TrueString : bool.FalseString, + long value => value.ToString(CultureInfo.InvariantCulture), - return parameters; - } + string value => value, + string?[] value => string.Join(", ", value), + + JsonElement value => value.ToString(), - return ImmutableList.Create(); + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0194)) + }; + + /// + /// Tries to get the child item corresponding to the specified name. + /// + /// The name of the child item. + /// An instance containing the item value. + /// true if the parameter could be found, false otherwise. + public bool TryGetNamedParameter(string name, out OpenIddictParameter value) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0192), nameof(name)); } - /// - /// Returns the representation of the current instance. - /// - /// The representation associated with the parameter value. - public override string? ToString() => Value switch + if (Value is JsonElement { ValueKind: JsonValueKind.Object } element && + element.TryGetProperty(name, out JsonElement property)) { - null => string.Empty, + value = new OpenIddictParameter(property); - bool value => value ? bool.TrueString : bool.FalseString, - long value => value.ToString(CultureInfo.InvariantCulture), + return true; + } - string value => value, - string?[] value => string.Join(", ", value), + value = default; - JsonElement value => value.ToString(), + return false; + } - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0194)) - }; + /// + /// Tries to get the child item corresponding to the specified index. + /// + /// The index of the child item. + /// An instance containing the item value. + /// true if the parameter could be found, false otherwise. + public bool TryGetUnnamedParameter(int index, out OpenIddictParameter value) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.GetResourceString(SR.ID0193)); + } - /// - /// Tries to get the child item corresponding to the specified name. - /// - /// The name of the child item. - /// An instance containing the item value. - /// true if the parameter could be found, false otherwise. - public bool TryGetNamedParameter(string name, out OpenIddictParameter value) + if (Value is string?[] array) { - if (string.IsNullOrEmpty(name)) + if (index >= array.Length) { - throw new ArgumentException(SR.GetResourceString(SR.ID0192), nameof(name)); + value = default; + + return false; } - if (Value is JsonElement { ValueKind: JsonValueKind.Object } element && - element.TryGetProperty(name, out JsonElement property)) + value = new OpenIddictParameter(array[index]); + + return true; + } + + if (Value is JsonElement { ValueKind: JsonValueKind.Array } element) + { + if (index >= element.GetArrayLength()) { - value = new OpenIddictParameter(property); + value = default; - return true; + return false; } - value = default; + value = new OpenIddictParameter(element[index]); - return false; + return true; } - /// - /// Tries to get the child item corresponding to the specified index. - /// - /// The index of the child item. - /// An instance containing the item value. - /// true if the parameter could be found, false otherwise. - public bool TryGetUnnamedParameter(int index, out OpenIddictParameter value) + value = default; + + return false; + } + + /// + /// Writes the parameter value to the specified JSON writer. + /// + /// The UTF-8 JSON writer. + public void WriteTo(Utf8JsonWriter writer) + { + if (writer is null) { - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.GetResourceString(SR.ID0193)); - } + throw new ArgumentNullException(nameof(writer)); + } - if (Value is string?[] array) - { - if (index >= array.Length) - { - value = default; + switch (Value) + { + // Note: undefined JsonElement values are assimilated to null values. + case null: + case JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined }: + writer.WriteNullValue(); + break; - return false; - } + case bool value: + writer.WriteBooleanValue(value); + break; - value = new OpenIddictParameter(array[index]); + case long value: + writer.WriteNumberValue(value); + break; - return true; - } + case string value: + writer.WriteStringValue(value); + break; - if (Value is JsonElement { ValueKind: JsonValueKind.Array } element) - { - if (index >= element.GetArrayLength()) - { - value = default; + case string?[] value: + writer.WriteStartArray(); - return false; + for (var index = 0; index < value.Length; index++) + { + writer.WriteStringValue(value[index]); } - value = new OpenIddictParameter(element[index]); + writer.WriteEndArray(); + break; - return true; - } - - value = default; + case JsonElement value: + value.WriteTo(writer); + break; - return false; + default: throw new InvalidOperationException(SR.GetResourceString(SR.ID0194)); } + } + + /// + /// Determines whether two instances are equal. + /// + /// The first instance. + /// The second instance. + /// true if the two instances are equal, false otherwise. + public static bool operator ==(OpenIddictParameter left, OpenIddictParameter right) => left.Equals(right); + + /// + /// Determines whether two instances are not equal. + /// + /// The first instance. + /// The second instance. + /// true if the two instances are not equal, false otherwise. + public static bool operator !=(OpenIddictParameter left, OpenIddictParameter right) => !left.Equals(right); + + /// + /// Converts an instance to a boolean. + /// + /// The parameter to convert. + /// The converted value. + public static explicit operator bool(OpenIddictParameter? parameter) + => ((bool?) parameter).GetValueOrDefault(); + + /// + /// Converts an instance to a nullable boolean. + /// + /// The parameter to convert. + /// The converted value. + public static explicit operator bool?(OpenIddictParameter? parameter) => parameter?.Value switch + { + // When the parameter is a null value or a JsonElement representing null, return null. + null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, + + // When the parameter is a boolean value, return it as-is. + bool value => value, + + // When the parameter is a string value, try to parse it. + string value => bool.TryParse(value, out var result) ? (bool?) result : null, + + // When the parameter is a JsonElement representing a boolean, return it as-is. + JsonElement { ValueKind: JsonValueKind.False } => false, + JsonElement { ValueKind: JsonValueKind.True } => true, + + // When the parameter is a JsonElement representing a string, try to parse it. + JsonElement { ValueKind: JsonValueKind.String } value + => bool.TryParse(value.GetString(), out var result) ? (bool?) result : null, + + // If the parameter is of a different type, return null to indicate the conversion failed. + _ => null + }; + + /// + /// Converts an instance to a . + /// + /// The parameter to convert. + /// The converted value. + public static explicit operator JsonElement(OpenIddictParameter? parameter) + { + return parameter?.Value switch + { + // When the parameter is a null value, return default. + null => default, + + // When the parameter is already a JsonElement, return it as-is. + JsonElement value => value, + + // When the parameter is a string starting with '{' or '[' (which would correspond + // to a JSON object or array), try to deserialize it to get a JsonElement instance. + string { Length: > 0 } value when value[0] is '{' or '[' => + DeserializeElement(value) ?? + DeserializeElement(SerializeObject(value)) ?? default, + + // Otherwise, serialize it to get a JsonElement instance. + var value => DeserializeElement(SerializeObject(value)) ?? default + }; - /// - /// Writes the parameter value to the specified JSON writer. - /// - /// The UTF-8 JSON writer. - public void WriteTo(Utf8JsonWriter writer) + static JsonElement? DeserializeElement(string value) { - if (writer is null) + try { - throw new ArgumentNullException(nameof(writer)); + using var document = JsonDocument.Parse(value); + return document.RootElement.Clone(); } - switch (Value) + catch (JsonException) { - // Note: undefined JsonElement values are assimilated to null values. - case null: - case JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined }: - writer.WriteNullValue(); - break; + return null; + } + } + + static string SerializeObject(object instance) + { + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); + switch (instance) + { case bool value: writer.WriteBooleanValue(value); break; @@ -545,356 +678,222 @@ namespace OpenIddict.Abstractions writer.WriteEndArray(); break; - case JsonElement value: - value.WriteTo(writer); - break; - default: throw new InvalidOperationException(SR.GetResourceString(SR.ID0194)); } - } - - /// - /// Determines whether two instances are equal. - /// - /// The first instance. - /// The second instance. - /// true if the two instances are equal, false otherwise. - public static bool operator ==(OpenIddictParameter left, OpenIddictParameter right) => left.Equals(right); - - /// - /// Determines whether two instances are not equal. - /// - /// The first instance. - /// The second instance. - /// true if the two instances are not equal, false otherwise. - public static bool operator !=(OpenIddictParameter left, OpenIddictParameter right) => !left.Equals(right); - - /// - /// Converts an instance to a boolean. - /// - /// The parameter to convert. - /// The converted value. - public static explicit operator bool(OpenIddictParameter? parameter) - => ((bool?) parameter).GetValueOrDefault(); - - /// - /// Converts an instance to a nullable boolean. - /// - /// The parameter to convert. - /// The converted value. - public static explicit operator bool?(OpenIddictParameter? parameter) => parameter?.Value switch - { - // When the parameter is a null value or a JsonElement representing null, return null. - null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, - // When the parameter is a boolean value, return it as-is. - bool value => value, + writer.Flush(); - // When the parameter is a string value, try to parse it. - string value => bool.TryParse(value, out var result) ? (bool?) result : null, - - // When the parameter is a JsonElement representing a boolean, return it as-is. - JsonElement { ValueKind: JsonValueKind.False } => false, - JsonElement { ValueKind: JsonValueKind.True } => true, - - // When the parameter is a JsonElement representing a string, try to parse it. - JsonElement { ValueKind: JsonValueKind.String } value - => bool.TryParse(value.GetString(), out var result) ? (bool?) result : null, - - // If the parameter is of a different type, return null to indicate the conversion failed. - _ => null - }; - - /// - /// Converts an instance to a . - /// - /// The parameter to convert. - /// The converted value. - public static explicit operator JsonElement(OpenIddictParameter? parameter) - { - return parameter?.Value switch - { - // When the parameter is a null value, return default. - null => default, + return Encoding.UTF8.GetString(stream.ToArray()); + } + } - // When the parameter is already a JsonElement, return it as-is. - JsonElement value => value, + /// + /// Converts an instance to a long integer. + /// + /// The parameter to convert. + /// The converted value. + public static explicit operator long(OpenIddictParameter? parameter) + => ((long?) parameter).GetValueOrDefault(); - // When the parameter is a string starting with '{' or '[' (which would correspond - // to a JSON object or array), try to deserialize it to get a JsonElement instance. - string { Length: > 0 } value when value[0] is '{' or '[' => - DeserializeElement(value) ?? - DeserializeElement(SerializeObject(value)) ?? default, + /// + /// Converts an instance to a nullable long integer. + /// + /// The parameter to convert. + /// The converted value. + public static explicit operator long?(OpenIddictParameter? parameter) => parameter?.Value switch + { + // When the parameter is a null value or a JsonElement representing null, return null. + null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, - // Otherwise, serialize it to get a JsonElement instance. - var value => DeserializeElement(SerializeObject(value)) ?? default - }; + // When the parameter is an integer, return it as-is. + long value => value, - static JsonElement? DeserializeElement(string value) - { - try - { - using var document = JsonDocument.Parse(value); - return document.RootElement.Clone(); - } + // When the parameter is a string value, try to parse it. + string value + => long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? (long?) result : null, - catch (JsonException) - { - return null; - } - } + // When the parameter is a JsonElement representing a number, return it as-is. + JsonElement { ValueKind: JsonValueKind.Number } value + => value.TryGetInt64(out var result) ? (long?) result : null, - static string SerializeObject(object instance) - { - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + // When the parameter is a JsonElement representing a string, try to parse it. + JsonElement { ValueKind: JsonValueKind.String } value + => long.TryParse(value.GetString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? (long?) result : null, - switch (instance) - { - case bool value: - writer.WriteBooleanValue(value); - break; + // If the parameter is of a different type, return null to indicate the conversion failed. + _ => null + }; - case long value: - writer.WriteNumberValue(value); - break; + /// + /// Converts an instance to a string. + /// + /// The parameter to convert. + /// The converted value. + public static explicit operator string?(OpenIddictParameter? parameter) => parameter?.Value switch + { + // When the parameter is a null value or a JsonElement representing null, return null. + null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, - case string value: - writer.WriteStringValue(value); - break; + // When the parameter is a string value, return it as-is. + string value => value, - case string?[] value: - writer.WriteStartArray(); + // When the parameter is a boolean value, use its string representation. + bool value => value.ToString(), - for (var index = 0; index < value.Length; index++) - { - writer.WriteStringValue(value[index]); - } + // When the parameter is an integer, use its string representation. + long value => value.ToString(CultureInfo.InvariantCulture), - writer.WriteEndArray(); - break; + // When the parameter is a JsonElement representing a string, return it as-is. + JsonElement { ValueKind: JsonValueKind.String } value => value.GetString(), - default: throw new InvalidOperationException(SR.GetResourceString(SR.ID0194)); - } + // When the parameter is a JsonElement representing a number, return its representation. + JsonElement { ValueKind: JsonValueKind.Number } value + => value.GetInt64().ToString(CultureInfo.InvariantCulture), - writer.Flush(); + // When the parameter is a JsonElement representing a boolean, return its representation. + JsonElement { ValueKind: JsonValueKind.False } => bool.FalseString, + JsonElement { ValueKind: JsonValueKind.True } => bool.TrueString, - return Encoding.UTF8.GetString(stream.ToArray()); - } - } + // If the parameter is of a different type, return null to indicate the conversion failed. + _ => null + }; - /// - /// Converts an instance to a long integer. - /// - /// The parameter to convert. - /// The converted value. - public static explicit operator long(OpenIddictParameter? parameter) - => ((long?) parameter).GetValueOrDefault(); - - /// - /// Converts an instance to a nullable long integer. - /// - /// The parameter to convert. - /// The converted value. - public static explicit operator long?(OpenIddictParameter? parameter) => parameter?.Value switch + /// + /// Converts an instance to an array of strings. + /// + /// The parameter to convert. + /// The converted value. + public static explicit operator string?[]?(OpenIddictParameter? parameter) + { + return parameter?.Value switch { // When the parameter is a null value or a JsonElement representing null, return null. null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, - // When the parameter is an integer, return it as-is. - long value => value, - - // When the parameter is a string value, try to parse it. - string value - => long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? (long?) result : null, - - // When the parameter is a JsonElement representing a number, return it as-is. - JsonElement { ValueKind: JsonValueKind.Number } value - => value.TryGetInt64(out var result) ? (long?) result : null, - - // When the parameter is a JsonElement representing a string, try to parse it. - JsonElement { ValueKind: JsonValueKind.String } value - => long.TryParse(value.GetString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? (long?) result : null, - - // If the parameter is of a different type, return null to indicate the conversion failed. - _ => null - }; - - /// - /// Converts an instance to a string. - /// - /// The parameter to convert. - /// The converted value. - public static explicit operator string?(OpenIddictParameter? parameter) => parameter?.Value switch - { - // When the parameter is a null value or a JsonElement representing null, return null. - null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, + // When the parameter is already an array of strings, return it as-is. + string?[] value => value, - // When the parameter is a string value, return it as-is. - string value => value, + // When the parameter is a string value, return an array with a single entry. + string value => new string?[] { value }, - // When the parameter is a boolean value, use its string representation. - bool value => value.ToString(), + // When the parameter is a boolean value, return an array with its string representation. + bool value => new string?[] { value.ToString() }, - // When the parameter is an integer, use its string representation. - long value => value.ToString(CultureInfo.InvariantCulture), + // When the parameter is an integer, return an array with its string representation. + long value => new string?[] { value.ToString(CultureInfo.InvariantCulture) }, - // When the parameter is a JsonElement representing a string, return it as-is. - JsonElement { ValueKind: JsonValueKind.String } value => value.GetString(), + // When the parameter is a JsonElement representing a string, return an array with a single entry. + JsonElement { ValueKind: JsonValueKind.String } value => new string?[] { value.GetString() }, - // When the parameter is a JsonElement representing a number, return its representation. + // When the parameter is a JsonElement representing a number, return an array with a single entry. JsonElement { ValueKind: JsonValueKind.Number } value - => value.GetInt64().ToString(CultureInfo.InvariantCulture), + => new string?[] { value.GetInt64().ToString(CultureInfo.InvariantCulture) }, + + // When the parameter is a JsonElement representing a boolean, return an array with a single entry. + JsonElement { ValueKind: JsonValueKind.False } => new string?[] { bool.FalseString }, + JsonElement { ValueKind: JsonValueKind.True } => new string?[] { bool.TrueString }, - // When the parameter is a JsonElement representing a boolean, return its representation. - JsonElement { ValueKind: JsonValueKind.False } => bool.FalseString, - JsonElement { ValueKind: JsonValueKind.True } => bool.TrueString, + // When the parameter is a JsonElement representing an array of strings, return it. + JsonElement { ValueKind: JsonValueKind.Array } value => CreateArray(value), // If the parameter is of a different type, return null to indicate the conversion failed. _ => null }; - /// - /// Converts an instance to an array of strings. - /// - /// The parameter to convert. - /// The converted value. - public static explicit operator string?[]?(OpenIddictParameter? parameter) + static string?[]? CreateArray(JsonElement value) { - return parameter?.Value switch - { - // When the parameter is a null value or a JsonElement representing null, return null. - null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => null, - - // When the parameter is already an array of strings, return it as-is. - string?[] value => value, - - // When the parameter is a string value, return an array with a single entry. - string value => new string?[] { value }, + var array = new string?[value.GetArrayLength()]; + using var enumerator = value.EnumerateArray(); - // When the parameter is a boolean value, return an array with its string representation. - bool value => new string?[] { value.ToString() }, - - // When the parameter is an integer, return an array with its string representation. - long value => new string?[] { value.ToString(CultureInfo.InvariantCulture) }, + for (var index = 0; enumerator.MoveNext(); index++) + { + var element = enumerator.Current; + if (element.ValueKind != JsonValueKind.String) + { + return null; + } - // When the parameter is a JsonElement representing a string, return an array with a single entry. - JsonElement { ValueKind: JsonValueKind.String } value => new string?[] { value.GetString() }, + array[index] = element.GetString(); + } - // When the parameter is a JsonElement representing a number, return an array with a single entry. - JsonElement { ValueKind: JsonValueKind.Number } value - => new string?[] { value.GetInt64().ToString(CultureInfo.InvariantCulture) }, + return array; + } + } - // When the parameter is a JsonElement representing a boolean, return an array with a single entry. - JsonElement { ValueKind: JsonValueKind.False } => new string?[] { bool.FalseString }, - JsonElement { ValueKind: JsonValueKind.True } => new string?[] { bool.TrueString }, + /// + /// Converts a boolean to an instance. + /// + /// The value to convert + /// An instance. + public static implicit operator OpenIddictParameter(bool value) => new OpenIddictParameter(value); - // When the parameter is a JsonElement representing an array of strings, return it. - JsonElement { ValueKind: JsonValueKind.Array } value => CreateArray(value), + /// + /// Converts a nullable boolean to an instance. + /// + /// The value to convert + /// An instance. + public static implicit operator OpenIddictParameter(bool? value) => new OpenIddictParameter(value); - // If the parameter is of a different type, return null to indicate the conversion failed. - _ => null - }; + /// + /// Converts a to an instance. + /// + /// The value to convert + /// An instance. + public static implicit operator OpenIddictParameter(JsonElement value) => new OpenIddictParameter(value); - static string?[]? CreateArray(JsonElement value) - { - var array = new string?[value.GetArrayLength()]; - using var enumerator = value.EnumerateArray(); + /// + /// Converts a long integer to an instance. + /// + /// The value to convert + /// An instance. + public static implicit operator OpenIddictParameter(long value) => new OpenIddictParameter(value); - for (var index = 0; enumerator.MoveNext(); index++) - { - var element = enumerator.Current; - if (element.ValueKind != JsonValueKind.String) - { - return null; - } + /// + /// Converts a nullable long integer to an instance. + /// + /// The value to convert + /// An instance. + public static implicit operator OpenIddictParameter(long? value) => new OpenIddictParameter(value); - array[index] = element.GetString(); - } + /// + /// Converts a string to an instance. + /// + /// The value to convert + /// An instance. + public static implicit operator OpenIddictParameter(string? value) => new OpenIddictParameter(value); - return array; - } - } + /// + /// Converts an array of strings to an instance. + /// + /// The value to convert + /// An instance. + public static implicit operator OpenIddictParameter(string?[]? value) => new OpenIddictParameter(value); - /// - /// Converts a boolean to an instance. - /// - /// The value to convert - /// An instance. - public static implicit operator OpenIddictParameter(bool value) => new OpenIddictParameter(value); - - /// - /// Converts a nullable boolean to an instance. - /// - /// The value to convert - /// An instance. - public static implicit operator OpenIddictParameter(bool? value) => new OpenIddictParameter(value); - - /// - /// Converts a to an instance. - /// - /// The value to convert - /// An instance. - public static implicit operator OpenIddictParameter(JsonElement value) => new OpenIddictParameter(value); - - /// - /// Converts a long integer to an instance. - /// - /// The value to convert - /// An instance. - public static implicit operator OpenIddictParameter(long value) => new OpenIddictParameter(value); - - /// - /// Converts a nullable long integer to an instance. - /// - /// The value to convert - /// An instance. - public static implicit operator OpenIddictParameter(long? value) => new OpenIddictParameter(value); - - /// - /// Converts a string to an instance. - /// - /// The value to convert - /// An instance. - public static implicit operator OpenIddictParameter(string? value) => new OpenIddictParameter(value); - - /// - /// Converts an array of strings to an instance. - /// - /// The value to convert - /// An instance. - public static implicit operator OpenIddictParameter(string?[]? value) => new OpenIddictParameter(value); - - /// - /// Determines whether a parameter is null or empty. - /// - /// The parameter. - /// true if the parameter is null or empty, false otherwise. - public static bool IsNullOrEmpty(OpenIddictParameter parameter) + /// + /// Determines whether a parameter is null or empty. + /// + /// The parameter. + /// true if the parameter is null or empty, false otherwise. + public static bool IsNullOrEmpty(OpenIddictParameter parameter) + { + return parameter.Value switch { - return parameter.Value switch - { - null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => true, + null or JsonElement { ValueKind: JsonValueKind.Null or JsonValueKind.Undefined } => true, - string value => string.IsNullOrEmpty(value), - string?[] value => value.Length == 0, + string value => string.IsNullOrEmpty(value), + string?[] value => value.Length == 0, - JsonElement { ValueKind: JsonValueKind.String } value => string.IsNullOrEmpty(value.GetString()), - JsonElement { ValueKind: JsonValueKind.Array } value => value.GetArrayLength() == 0, - JsonElement { ValueKind: JsonValueKind.Object } value => IsEmptyNode(value), + JsonElement { ValueKind: JsonValueKind.String } value => string.IsNullOrEmpty(value.GetString()), + JsonElement { ValueKind: JsonValueKind.Array } value => value.GetArrayLength() == 0, + JsonElement { ValueKind: JsonValueKind.Object } value => IsEmptyNode(value), - _ => false - }; + _ => false + }; - static bool IsEmptyNode(JsonElement value) - { - using var enumerator = value.EnumerateObject(); - return !enumerator.MoveNext(); - } + static bool IsEmptyNode(JsonElement value) + { + using var enumerator = value.EnumerateObject(); + return !enumerator.MoveNext(); } } } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs index cd2e222d..eaff6910 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictRequest.cs @@ -10,445 +10,444 @@ using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Primitives; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Represents a generic OpenIddict request. +/// +/// +/// Security notice: developers instantiating this type are responsible of ensuring that the +/// imported parameters are safe and won't cause the resulting message to grow abnormally, +/// which may result in an excessive memory consumption and a potential denial of service. +/// +[DebuggerDisplay("Parameters: {Parameters.Count}")] +[JsonConverter(typeof(OpenIddictConverter))] +public class OpenIddictRequest : OpenIddictMessage { /// - /// Represents a generic OpenIddict request. - /// - /// - /// Security notice: developers instantiating this type are responsible of ensuring that the - /// imported parameters are safe and won't cause the resulting message to grow abnormally, - /// which may result in an excessive memory consumption and a potential denial of service. - /// - [DebuggerDisplay("Parameters: {Parameters.Count}")] - [JsonConverter(typeof(OpenIddictConverter))] - public class OpenIddictRequest : OpenIddictMessage - { - /// - /// Initializes a new OpenIddict request. - /// - public OpenIddictRequest() - : base() - { - } - - /// - /// Initializes a new OpenIddict request. - /// - /// The request parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictRequest(JsonElement parameters) - : base(parameters) - { - } - - /// - /// Initializes a new OpenIddict request. - /// - /// The request parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictRequest(IEnumerable> parameters) - : base(parameters) - { - } - - /// - /// Initializes a new OpenIddict request. - /// - /// The request parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictRequest(IEnumerable> parameters) - : base(parameters) - { - } - - /// - /// Initializes a new OpenIddict request. - /// - /// The request parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictRequest(IEnumerable> parameters) - : base(parameters) - { - } - - /// - /// Initializes a new OpenIddict request. - /// - /// The request parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictRequest(IEnumerable> parameters) - : base(parameters) - { - } - - /// - /// Gets or sets the "access_token" parameter. - /// - public string? AccessToken - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.AccessToken); - set => SetParameter(OpenIddictConstants.Parameters.AccessToken, value); - } - - /// - /// Gets or sets the "acr_values" parameter. - /// - public string? AcrValues - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.AcrValues); - set => SetParameter(OpenIddictConstants.Parameters.AcrValues, value); - } - - /// - /// Gets or sets the "assertion" parameter. - /// - public string? Assertion - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Assertion); - set => SetParameter(OpenIddictConstants.Parameters.Assertion, value); - } - - /// - /// Gets or sets the "audience" parameters. - /// - public string[]? Audiences - { - get => (string[]?) GetParameter(OpenIddictConstants.Parameters.Audience); - set => SetParameter(OpenIddictConstants.Parameters.Audience, value); - } - - /// - /// Gets or sets the "claims" parameter. - /// - public JsonElement Claims - { - get => (JsonElement) GetParameter(OpenIddictConstants.Parameters.Claims); - set => SetParameter(OpenIddictConstants.Parameters.Claims, value); - } - - /// - /// Gets or sets the "claims_locales" parameter. - /// - public string? ClaimsLocales - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.ClaimsLocales); - set => SetParameter(OpenIddictConstants.Parameters.ClaimsLocales, value); - } - - /// - /// Gets or sets the "client_assertion" parameter. - /// - public string? ClientAssertion - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.ClientAssertion); - set => SetParameter(OpenIddictConstants.Parameters.ClientAssertion, value); - } - - /// - /// Gets or sets the "client_assertion_type" parameter. - /// - public string? ClientAssertionType - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.ClientAssertionType); - set => SetParameter(OpenIddictConstants.Parameters.ClientAssertionType, value); - } - - /// - /// Gets or sets the "client_id" parameter. - /// - public string? ClientId - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.ClientId); - set => SetParameter(OpenIddictConstants.Parameters.ClientId, value); - } - - /// - /// Gets or sets the "client_secret" parameter. - /// - public string? ClientSecret - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.ClientSecret); - set => SetParameter(OpenIddictConstants.Parameters.ClientSecret, value); - } - - /// - /// Gets or sets the "code" parameter. - /// - public string? Code - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Code); - set => SetParameter(OpenIddictConstants.Parameters.Code, value); - } - - /// - /// Gets or sets the "code_challenge" parameter. - /// - public string? CodeChallenge - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.CodeChallenge); - set => SetParameter(OpenIddictConstants.Parameters.CodeChallenge, value); - } - - /// - /// Gets or sets the "code_challenge_method" parameter. - /// - public string? CodeChallengeMethod - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.CodeChallengeMethod); - set => SetParameter(OpenIddictConstants.Parameters.CodeChallengeMethod, value); - } - - /// - /// Gets or sets the "code_verifier" parameter. - /// - public string? CodeVerifier - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.CodeVerifier); - set => SetParameter(OpenIddictConstants.Parameters.CodeVerifier, value); - } - - /// - /// Gets or sets the "device_code" parameter. - /// - public string? DeviceCode - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.DeviceCode); - set => SetParameter(OpenIddictConstants.Parameters.DeviceCode, value); - } - - /// - /// Gets or sets the "display" parameter. - /// - public string? Display - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Display); - set => SetParameter(OpenIddictConstants.Parameters.Display, value); - } - - /// - /// Gets or sets the "grant_type" parameter. - /// - public string? GrantType - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.GrantType); - set => SetParameter(OpenIddictConstants.Parameters.GrantType, value); - } - - /// - /// Gets or sets the "identity_provider" parameter. - /// - public string? IdentityProvider - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.IdentityProvider); - set => SetParameter(OpenIddictConstants.Parameters.IdentityProvider, value); - } - - /// - /// Gets or sets the "id_token_hint" parameter. - /// - public string? IdTokenHint - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.IdTokenHint); - set => SetParameter(OpenIddictConstants.Parameters.IdTokenHint, value); - } - - /// - /// Gets or sets the "login_hint" parameter. - /// - public string? LoginHint - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.LoginHint); - set => SetParameter(OpenIddictConstants.Parameters.LoginHint, value); - } - - /// - /// Gets or sets the "max_age" parameter. - /// - public long? MaxAge - { - get => (long?) GetParameter(OpenIddictConstants.Parameters.MaxAge); - set => SetParameter(OpenIddictConstants.Parameters.MaxAge, value); - } - - /// - /// Gets or sets the "nonce" parameter. - /// - public string? Nonce - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Nonce); - set => SetParameter(OpenIddictConstants.Parameters.Nonce, value); - } - - /// - /// Gets or sets the "password" parameter. - /// - public string? Password - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Password); - set => SetParameter(OpenIddictConstants.Parameters.Password, value); - } - - /// - /// Gets or sets the "post_logout_redirect_uri" parameter. - /// - public string? PostLogoutRedirectUri - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.PostLogoutRedirectUri); - set => SetParameter(OpenIddictConstants.Parameters.PostLogoutRedirectUri, value); - } - - /// - /// Gets or sets the "prompt" parameter. - /// - public string? Prompt - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Prompt); - set => SetParameter(OpenIddictConstants.Parameters.Prompt, value); - } - - /// - /// Gets or sets the "redirect_uri" parameter. - /// - public string? RedirectUri - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.RedirectUri); - set => SetParameter(OpenIddictConstants.Parameters.RedirectUri, value); - } - - /// - /// Gets or sets the "refresh_token" parameter. - /// - public string? RefreshToken - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.RefreshToken); - set => SetParameter(OpenIddictConstants.Parameters.RefreshToken, value); - } - - /// - /// Gets or sets the "request" parameter. - /// - public string? Request - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Request); - set => SetParameter(OpenIddictConstants.Parameters.Request, value); - } - - /// - /// Gets or sets the "request_id" parameter. - /// - public string? RequestId - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.RequestId); - set => SetParameter(OpenIddictConstants.Parameters.RequestId, value); - } - - /// - /// Gets or sets the "request_uri" parameter. - /// - public string? RequestUri - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.RequestUri); - set => SetParameter(OpenIddictConstants.Parameters.RequestUri, value); - } - - /// - /// Gets or sets the "resource" parameters. - /// - public string[]? Resources - { - get => (string[]?) GetParameter(OpenIddictConstants.Parameters.Resource); - set => SetParameter(OpenIddictConstants.Parameters.Resource, value); - } - - /// - /// Gets or sets the "response_mode" parameter. - /// - public string? ResponseMode - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.ResponseMode); - set => SetParameter(OpenIddictConstants.Parameters.ResponseMode, value); - } - - /// - /// Gets or sets the "response_type" parameter. - /// - public string? ResponseType - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.ResponseType); - set => SetParameter(OpenIddictConstants.Parameters.ResponseType, value); - } - - /// - /// Gets or sets the "scope" parameter. - /// - public string? Scope - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Scope); - set => SetParameter(OpenIddictConstants.Parameters.Scope, value); - } - - /// - /// Gets or sets the "state" parameter. - /// - public string? State - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.State); - set => SetParameter(OpenIddictConstants.Parameters.State, value); - } - - /// - /// Gets or sets the "token" parameter. - /// - public string? Token - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Token); - set => SetParameter(OpenIddictConstants.Parameters.Token, value); - } - - /// - /// Gets or sets the "token_type_hint" parameter. - /// - public string? TokenTypeHint - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.TokenTypeHint); - set => SetParameter(OpenIddictConstants.Parameters.TokenTypeHint, value); - } - - /// - /// Gets or sets the "registration" parameter. - /// - public JsonElement Registration - { - get => (JsonElement) GetParameter(OpenIddictConstants.Parameters.Registration); - set => SetParameter(OpenIddictConstants.Parameters.Registration, value); - } - - /// - /// Gets or sets the "ui_locales" parameter. - /// - public string? UiLocales - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.UiLocales); - set => SetParameter(OpenIddictConstants.Parameters.UiLocales, value); - } - - /// - /// Gets or sets the "user_code" parameter. - /// - public string? UserCode - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.UserCode); - set => SetParameter(OpenIddictConstants.Parameters.UserCode, value); - } - - /// - /// Gets or sets the "username" parameter. - /// - public string? Username - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Username); - set => SetParameter(OpenIddictConstants.Parameters.Username, value); - } + /// Initializes a new OpenIddict request. + /// + public OpenIddictRequest() + : base() + { + } + + /// + /// Initializes a new OpenIddict request. + /// + /// The request parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictRequest(JsonElement parameters) + : base(parameters) + { + } + + /// + /// Initializes a new OpenIddict request. + /// + /// The request parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictRequest(IEnumerable> parameters) + : base(parameters) + { + } + + /// + /// Initializes a new OpenIddict request. + /// + /// The request parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictRequest(IEnumerable> parameters) + : base(parameters) + { + } + + /// + /// Initializes a new OpenIddict request. + /// + /// The request parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictRequest(IEnumerable> parameters) + : base(parameters) + { + } + + /// + /// Initializes a new OpenIddict request. + /// + /// The request parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictRequest(IEnumerable> parameters) + : base(parameters) + { + } + + /// + /// Gets or sets the "access_token" parameter. + /// + public string? AccessToken + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.AccessToken); + set => SetParameter(OpenIddictConstants.Parameters.AccessToken, value); + } + + /// + /// Gets or sets the "acr_values" parameter. + /// + public string? AcrValues + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.AcrValues); + set => SetParameter(OpenIddictConstants.Parameters.AcrValues, value); + } + + /// + /// Gets or sets the "assertion" parameter. + /// + public string? Assertion + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Assertion); + set => SetParameter(OpenIddictConstants.Parameters.Assertion, value); + } + + /// + /// Gets or sets the "audience" parameters. + /// + public string[]? Audiences + { + get => (string[]?) GetParameter(OpenIddictConstants.Parameters.Audience); + set => SetParameter(OpenIddictConstants.Parameters.Audience, value); + } + + /// + /// Gets or sets the "claims" parameter. + /// + public JsonElement Claims + { + get => (JsonElement) GetParameter(OpenIddictConstants.Parameters.Claims); + set => SetParameter(OpenIddictConstants.Parameters.Claims, value); + } + + /// + /// Gets or sets the "claims_locales" parameter. + /// + public string? ClaimsLocales + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.ClaimsLocales); + set => SetParameter(OpenIddictConstants.Parameters.ClaimsLocales, value); + } + + /// + /// Gets or sets the "client_assertion" parameter. + /// + public string? ClientAssertion + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.ClientAssertion); + set => SetParameter(OpenIddictConstants.Parameters.ClientAssertion, value); + } + + /// + /// Gets or sets the "client_assertion_type" parameter. + /// + public string? ClientAssertionType + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.ClientAssertionType); + set => SetParameter(OpenIddictConstants.Parameters.ClientAssertionType, value); + } + + /// + /// Gets or sets the "client_id" parameter. + /// + public string? ClientId + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.ClientId); + set => SetParameter(OpenIddictConstants.Parameters.ClientId, value); + } + + /// + /// Gets or sets the "client_secret" parameter. + /// + public string? ClientSecret + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.ClientSecret); + set => SetParameter(OpenIddictConstants.Parameters.ClientSecret, value); + } + + /// + /// Gets or sets the "code" parameter. + /// + public string? Code + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Code); + set => SetParameter(OpenIddictConstants.Parameters.Code, value); + } + + /// + /// Gets or sets the "code_challenge" parameter. + /// + public string? CodeChallenge + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.CodeChallenge); + set => SetParameter(OpenIddictConstants.Parameters.CodeChallenge, value); + } + + /// + /// Gets or sets the "code_challenge_method" parameter. + /// + public string? CodeChallengeMethod + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.CodeChallengeMethod); + set => SetParameter(OpenIddictConstants.Parameters.CodeChallengeMethod, value); + } + + /// + /// Gets or sets the "code_verifier" parameter. + /// + public string? CodeVerifier + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.CodeVerifier); + set => SetParameter(OpenIddictConstants.Parameters.CodeVerifier, value); + } + + /// + /// Gets or sets the "device_code" parameter. + /// + public string? DeviceCode + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.DeviceCode); + set => SetParameter(OpenIddictConstants.Parameters.DeviceCode, value); + } + + /// + /// Gets or sets the "display" parameter. + /// + public string? Display + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Display); + set => SetParameter(OpenIddictConstants.Parameters.Display, value); + } + + /// + /// Gets or sets the "grant_type" parameter. + /// + public string? GrantType + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.GrantType); + set => SetParameter(OpenIddictConstants.Parameters.GrantType, value); + } + + /// + /// Gets or sets the "identity_provider" parameter. + /// + public string? IdentityProvider + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.IdentityProvider); + set => SetParameter(OpenIddictConstants.Parameters.IdentityProvider, value); + } + + /// + /// Gets or sets the "id_token_hint" parameter. + /// + public string? IdTokenHint + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.IdTokenHint); + set => SetParameter(OpenIddictConstants.Parameters.IdTokenHint, value); + } + + /// + /// Gets or sets the "login_hint" parameter. + /// + public string? LoginHint + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.LoginHint); + set => SetParameter(OpenIddictConstants.Parameters.LoginHint, value); + } + + /// + /// Gets or sets the "max_age" parameter. + /// + public long? MaxAge + { + get => (long?) GetParameter(OpenIddictConstants.Parameters.MaxAge); + set => SetParameter(OpenIddictConstants.Parameters.MaxAge, value); + } + + /// + /// Gets or sets the "nonce" parameter. + /// + public string? Nonce + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Nonce); + set => SetParameter(OpenIddictConstants.Parameters.Nonce, value); + } + + /// + /// Gets or sets the "password" parameter. + /// + public string? Password + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Password); + set => SetParameter(OpenIddictConstants.Parameters.Password, value); + } + + /// + /// Gets or sets the "post_logout_redirect_uri" parameter. + /// + public string? PostLogoutRedirectUri + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.PostLogoutRedirectUri); + set => SetParameter(OpenIddictConstants.Parameters.PostLogoutRedirectUri, value); + } + + /// + /// Gets or sets the "prompt" parameter. + /// + public string? Prompt + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Prompt); + set => SetParameter(OpenIddictConstants.Parameters.Prompt, value); + } + + /// + /// Gets or sets the "redirect_uri" parameter. + /// + public string? RedirectUri + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.RedirectUri); + set => SetParameter(OpenIddictConstants.Parameters.RedirectUri, value); + } + + /// + /// Gets or sets the "refresh_token" parameter. + /// + public string? RefreshToken + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.RefreshToken); + set => SetParameter(OpenIddictConstants.Parameters.RefreshToken, value); + } + + /// + /// Gets or sets the "request" parameter. + /// + public string? Request + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Request); + set => SetParameter(OpenIddictConstants.Parameters.Request, value); + } + + /// + /// Gets or sets the "request_id" parameter. + /// + public string? RequestId + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.RequestId); + set => SetParameter(OpenIddictConstants.Parameters.RequestId, value); + } + + /// + /// Gets or sets the "request_uri" parameter. + /// + public string? RequestUri + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.RequestUri); + set => SetParameter(OpenIddictConstants.Parameters.RequestUri, value); + } + + /// + /// Gets or sets the "resource" parameters. + /// + public string[]? Resources + { + get => (string[]?) GetParameter(OpenIddictConstants.Parameters.Resource); + set => SetParameter(OpenIddictConstants.Parameters.Resource, value); + } + + /// + /// Gets or sets the "response_mode" parameter. + /// + public string? ResponseMode + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.ResponseMode); + set => SetParameter(OpenIddictConstants.Parameters.ResponseMode, value); + } + + /// + /// Gets or sets the "response_type" parameter. + /// + public string? ResponseType + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.ResponseType); + set => SetParameter(OpenIddictConstants.Parameters.ResponseType, value); + } + + /// + /// Gets or sets the "scope" parameter. + /// + public string? Scope + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Scope); + set => SetParameter(OpenIddictConstants.Parameters.Scope, value); + } + + /// + /// Gets or sets the "state" parameter. + /// + public string? State + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.State); + set => SetParameter(OpenIddictConstants.Parameters.State, value); + } + + /// + /// Gets or sets the "token" parameter. + /// + public string? Token + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Token); + set => SetParameter(OpenIddictConstants.Parameters.Token, value); + } + + /// + /// Gets or sets the "token_type_hint" parameter. + /// + public string? TokenTypeHint + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.TokenTypeHint); + set => SetParameter(OpenIddictConstants.Parameters.TokenTypeHint, value); + } + + /// + /// Gets or sets the "registration" parameter. + /// + public JsonElement Registration + { + get => (JsonElement) GetParameter(OpenIddictConstants.Parameters.Registration); + set => SetParameter(OpenIddictConstants.Parameters.Registration, value); + } + + /// + /// Gets or sets the "ui_locales" parameter. + /// + public string? UiLocales + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.UiLocales); + set => SetParameter(OpenIddictConstants.Parameters.UiLocales, value); + } + + /// + /// Gets or sets the "user_code" parameter. + /// + public string? UserCode + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.UserCode); + set => SetParameter(OpenIddictConstants.Parameters.UserCode, value); + } + + /// + /// Gets or sets the "username" parameter. + /// + public string? Username + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Username); + set => SetParameter(OpenIddictConstants.Parameters.Username, value); } } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs index bdf1beda..442a1381 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictResponse.cs @@ -10,193 +10,192 @@ using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Primitives; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Represents a generic OpenIddict response. +/// +/// +/// Security notice: developers instantiating this type are responsible of ensuring that the +/// imported parameters are safe and won't cause the resulting message to grow abnormally, +/// which may result in an excessive memory consumption and a potential denial of service. +/// +[DebuggerDisplay("Parameters: {Parameters.Count}")] +[JsonConverter(typeof(OpenIddictConverter))] +public class OpenIddictResponse : OpenIddictMessage { /// - /// Represents a generic OpenIddict response. - /// - /// - /// Security notice: developers instantiating this type are responsible of ensuring that the - /// imported parameters are safe and won't cause the resulting message to grow abnormally, - /// which may result in an excessive memory consumption and a potential denial of service. - /// - [DebuggerDisplay("Parameters: {Parameters.Count}")] - [JsonConverter(typeof(OpenIddictConverter))] - public class OpenIddictResponse : OpenIddictMessage - { - /// - /// Initializes a new OpenIddict response. - /// - public OpenIddictResponse() - : base() - { - } - - /// - /// Initializes a new OpenIddict response. - /// - /// The response parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictResponse(JsonElement parameters) - : base(parameters) - { - } - - /// - /// Initializes a new OpenIddict response. - /// - /// The response parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictResponse(IEnumerable> parameters) - : base(parameters) - { - } - - /// - /// Initializes a new OpenIddict response. - /// - /// The response parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictResponse(IEnumerable> parameters) - : base(parameters) - { - } - - /// - /// Initializes a new OpenIddict response. - /// - /// The response parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictResponse(IEnumerable> parameters) - : base(parameters) - { - } - - /// - /// Initializes a new OpenIddict response. - /// - /// The response parameters. - /// Parameters with a null or empty key are always ignored. - public OpenIddictResponse(IEnumerable> parameters) - : base(parameters) - { - } - - /// - /// Gets or sets the "access_token" parameter. - /// - public string? AccessToken - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.AccessToken); - set => SetParameter(OpenIddictConstants.Parameters.AccessToken, value); - } - - /// - /// Gets or sets the "code" parameter. - /// - public string? Code - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Code); - set => SetParameter(OpenIddictConstants.Parameters.Code, value); - } - - /// - /// Gets or sets the "device_code" parameter. - /// - public string? DeviceCode - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.DeviceCode); - set => SetParameter(OpenIddictConstants.Parameters.DeviceCode, value); - } - - /// - /// Gets or sets the "error" parameter. - /// - public string? Error - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Error); - set => SetParameter(OpenIddictConstants.Parameters.Error, value); - } - - /// - /// Gets or sets the "error_description" parameter. - /// - public string? ErrorDescription - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.ErrorDescription); - set => SetParameter(OpenIddictConstants.Parameters.ErrorDescription, value); - } - - /// - /// Gets or sets the "error_uri" parameter. - /// - public string? ErrorUri - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.ErrorUri); - set => SetParameter(OpenIddictConstants.Parameters.ErrorUri, value); - } - - /// - /// Gets or sets the "expires_in" parameter. - /// - public long? ExpiresIn - { - get => (long?) GetParameter(OpenIddictConstants.Parameters.ExpiresIn); - set => SetParameter(OpenIddictConstants.Parameters.ExpiresIn, value); - } - - /// - /// Gets or sets the "id_token" parameter. - /// - public string? IdToken - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.IdToken); - set => SetParameter(OpenIddictConstants.Parameters.IdToken, value); - } - - /// - /// Gets or sets the "refresh_token" parameter. - /// - public string? RefreshToken - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.RefreshToken); - set => SetParameter(OpenIddictConstants.Parameters.RefreshToken, value); - } - - /// - /// Gets or sets the "scope" parameter. - /// - public string? Scope - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.Scope); - set => SetParameter(OpenIddictConstants.Parameters.Scope, value); - } - - /// - /// Gets or sets the "state" parameter. - /// - public string? State - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.State); - set => SetParameter(OpenIddictConstants.Parameters.State, value); - } - - /// - /// Gets or sets the "token_type" parameter. - /// - public string? TokenType - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.TokenType); - set => SetParameter(OpenIddictConstants.Parameters.TokenType, value); - } - - /// - /// Gets or sets the "user_code" parameter. - /// - public string? UserCode - { - get => (string?) GetParameter(OpenIddictConstants.Parameters.UserCode); - set => SetParameter(OpenIddictConstants.Parameters.UserCode, value); - } + /// Initializes a new OpenIddict response. + /// + public OpenIddictResponse() + : base() + { + } + + /// + /// Initializes a new OpenIddict response. + /// + /// The response parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictResponse(JsonElement parameters) + : base(parameters) + { + } + + /// + /// Initializes a new OpenIddict response. + /// + /// The response parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictResponse(IEnumerable> parameters) + : base(parameters) + { + } + + /// + /// Initializes a new OpenIddict response. + /// + /// The response parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictResponse(IEnumerable> parameters) + : base(parameters) + { + } + + /// + /// Initializes a new OpenIddict response. + /// + /// The response parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictResponse(IEnumerable> parameters) + : base(parameters) + { + } + + /// + /// Initializes a new OpenIddict response. + /// + /// The response parameters. + /// Parameters with a null or empty key are always ignored. + public OpenIddictResponse(IEnumerable> parameters) + : base(parameters) + { + } + + /// + /// Gets or sets the "access_token" parameter. + /// + public string? AccessToken + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.AccessToken); + set => SetParameter(OpenIddictConstants.Parameters.AccessToken, value); + } + + /// + /// Gets or sets the "code" parameter. + /// + public string? Code + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Code); + set => SetParameter(OpenIddictConstants.Parameters.Code, value); + } + + /// + /// Gets or sets the "device_code" parameter. + /// + public string? DeviceCode + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.DeviceCode); + set => SetParameter(OpenIddictConstants.Parameters.DeviceCode, value); + } + + /// + /// Gets or sets the "error" parameter. + /// + public string? Error + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Error); + set => SetParameter(OpenIddictConstants.Parameters.Error, value); + } + + /// + /// Gets or sets the "error_description" parameter. + /// + public string? ErrorDescription + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.ErrorDescription); + set => SetParameter(OpenIddictConstants.Parameters.ErrorDescription, value); + } + + /// + /// Gets or sets the "error_uri" parameter. + /// + public string? ErrorUri + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.ErrorUri); + set => SetParameter(OpenIddictConstants.Parameters.ErrorUri, value); + } + + /// + /// Gets or sets the "expires_in" parameter. + /// + public long? ExpiresIn + { + get => (long?) GetParameter(OpenIddictConstants.Parameters.ExpiresIn); + set => SetParameter(OpenIddictConstants.Parameters.ExpiresIn, value); + } + + /// + /// Gets or sets the "id_token" parameter. + /// + public string? IdToken + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.IdToken); + set => SetParameter(OpenIddictConstants.Parameters.IdToken, value); + } + + /// + /// Gets or sets the "refresh_token" parameter. + /// + public string? RefreshToken + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.RefreshToken); + set => SetParameter(OpenIddictConstants.Parameters.RefreshToken, value); + } + + /// + /// Gets or sets the "scope" parameter. + /// + public string? Scope + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.Scope); + set => SetParameter(OpenIddictConstants.Parameters.Scope, value); + } + + /// + /// Gets or sets the "state" parameter. + /// + public string? State + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.State); + set => SetParameter(OpenIddictConstants.Parameters.State, value); + } + + /// + /// Gets or sets the "token_type" parameter. + /// + public string? TokenType + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.TokenType); + set => SetParameter(OpenIddictConstants.Parameters.TokenType, value); + } + + /// + /// Gets or sets the "user_code" parameter. + /// + public string? UserCode + { + get => (string?) GetParameter(OpenIddictConstants.Parameters.UserCode); + set => SetParameter(OpenIddictConstants.Parameters.UserCode, value); } } diff --git a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictApplicationStoreResolver.cs b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictApplicationStoreResolver.cs index 1724a255..1c2b0aa1 100644 --- a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictApplicationStoreResolver.cs +++ b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictApplicationStoreResolver.cs @@ -1,18 +1,17 @@ using System; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Exposes a method allowing to resolve an application store. +/// +public interface IOpenIddictApplicationStoreResolver { /// - /// Exposes a method allowing to resolve an application store. + /// Returns an application store compatible with the specified application type or throws an + /// if no store can be built using the specified type. /// - public interface IOpenIddictApplicationStoreResolver - { - /// - /// Returns an application store compatible with the specified application type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Application entity. - /// An . - IOpenIddictApplicationStore Get() where TApplication : class; - } + /// The type of the Application entity. + /// An . + IOpenIddictApplicationStore Get() where TApplication : class; } diff --git a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictAuthorizationStoreResolver.cs b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictAuthorizationStoreResolver.cs index 769ecd24..bdab1c41 100644 --- a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictAuthorizationStoreResolver.cs +++ b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictAuthorizationStoreResolver.cs @@ -1,18 +1,17 @@ using System; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Exposes a method allowing to resolve an authorization store. +/// +public interface IOpenIddictAuthorizationStoreResolver { /// - /// Exposes a method allowing to resolve an authorization store. + /// Returns an authorization store compatible with the specified authorization type or throws an + /// if no store can be built using the specified type. /// - public interface IOpenIddictAuthorizationStoreResolver - { - /// - /// Returns an authorization store compatible with the specified authorization type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Authorization entity. - /// An . - IOpenIddictAuthorizationStore Get() where TAuthorization : class; - } + /// The type of the Authorization entity. + /// An . + IOpenIddictAuthorizationStore Get() where TAuthorization : class; } diff --git a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictScopeStoreResolver.cs b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictScopeStoreResolver.cs index 9da948de..c40ab517 100644 --- a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictScopeStoreResolver.cs +++ b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictScopeStoreResolver.cs @@ -1,18 +1,17 @@ using System; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Exposes a method allowing to resolve a scope store. +/// +public interface IOpenIddictScopeStoreResolver { /// - /// Exposes a method allowing to resolve a scope store. + /// Returns a scope store compatible with the specified scope type or throws an + /// if no store can be built using the specified type. /// - public interface IOpenIddictScopeStoreResolver - { - /// - /// Returns a scope store compatible with the specified scope type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Scope entity. - /// An . - IOpenIddictScopeStore Get() where TScope : class; - } + /// The type of the Scope entity. + /// An . + IOpenIddictScopeStore Get() where TScope : class; } diff --git a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictTokenStoreResolver.cs b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictTokenStoreResolver.cs index 2bd5f8de..aa31f2d1 100644 --- a/src/OpenIddict.Abstractions/Resolvers/IOpenIddictTokenStoreResolver.cs +++ b/src/OpenIddict.Abstractions/Resolvers/IOpenIddictTokenStoreResolver.cs @@ -1,18 +1,17 @@ using System; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Exposes a method allowing to resolve a token store. +/// +public interface IOpenIddictTokenStoreResolver { /// - /// Exposes a method allowing to resolve a token store. + /// Returns a token store compatible with the specified token type or throws an + /// if no store can be built using the specified type. /// - public interface IOpenIddictTokenStoreResolver - { - /// - /// Returns a token store compatible with the specified token type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Token entity. - /// An . - IOpenIddictTokenStore Get() where TToken : class; - } + /// The type of the Token entity. + /// An . + IOpenIddictTokenStore Get() where TToken : class; } diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs index 668ce829..c71ca44a 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictApplicationStore.cs @@ -13,383 +13,382 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to manage the applications stored in a database. +/// +/// The type of the Application entity. +public interface IOpenIddictApplicationStore where TApplication : class { /// - /// Provides methods allowing to manage the applications stored in a database. - /// - /// The type of the Application entity. - public interface IOpenIddictApplicationStore where TApplication : class - { - /// - /// Determines the number of applications that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of applications in the database. - /// - ValueTask CountAsync(CancellationToken cancellationToken); - - /// - /// Determines the number of applications that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of applications that match the specified query. - /// - ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken); - - /// - /// Creates a new application. - /// - /// The application to create. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Removes an existing application. - /// - /// The application to delete. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves an application using its unique identifier. - /// - /// The unique identifier associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client application corresponding to the identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); - - /// - /// Retrieves an application using its client identifier. - /// - /// The client identifier associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client application corresponding to the identifier. - /// - ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken); - - /// - /// Retrieves all the applications associated with the specified post_logout_redirect_uri. - /// - /// The post_logout_redirect_uri associated with the applications. - /// The that can be used to abort the operation. - /// The client applications corresponding to the specified post_logout_redirect_uri. - IAsyncEnumerable FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken); - - /// - /// Retrieves all the applications associated with the specified redirect_uri. - /// - /// The redirect_uri associated with the applications. - /// The that can be used to abort the operation. - /// The client applications corresponding to the specified redirect_uri. - IAsyncEnumerable FindByRedirectUriAsync(string address, CancellationToken cancellationToken); - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken); - - /// - /// Retrieves the client identifier associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client identifier associated with the application. - /// - ValueTask GetClientIdAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the client secret associated with an application. - /// Note: depending on the manager used to create the application, - /// the client secret may be hashed for security reasons. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client secret associated with the application. - /// - ValueTask GetClientSecretAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the client type associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client type of the application (by default, "public"). - /// - ValueTask GetClientTypeAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the consent type associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the consent type of the application (by default, "explicit"). - /// - ValueTask GetConsentTypeAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the display name associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the display name associated with the application. - /// - ValueTask GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the localized display names associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the localized display names associated with the application. - /// - ValueTask> GetDisplayNamesAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the unique identifier associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the application. - /// - ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the permissions associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the permissions associated with the application. - /// - ValueTask> GetPermissionsAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the logout callback addresses associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the post_logout_redirect_uri associated with the application. - /// - ValueTask> GetPostLogoutRedirectUrisAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the additional properties associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the application. - /// - ValueTask> GetPropertiesAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the callback addresses associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the redirect_uri associated with the application. - /// - ValueTask> GetRedirectUrisAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Retrieves the requirements associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the requirements associated with the application. - /// - ValueTask> GetRequirementsAsync(TApplication application, CancellationToken cancellationToken); - - /// - /// Instantiates a new application. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the instantiated application, that can be persisted in the database. - /// - ValueTask InstantiateAsync(CancellationToken cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken); - - /// - /// Sets the client identifier associated with an application. - /// - /// The application. - /// The client identifier associated with the application. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetClientIdAsync(TApplication application, string? identifier, CancellationToken cancellationToken); - - /// - /// Sets the client secret associated with an application. - /// Note: depending on the manager used to create the application, - /// the client secret may be hashed for security reasons. - /// - /// The application. - /// The client secret associated with the application. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetClientSecretAsync(TApplication application, string? secret, CancellationToken cancellationToken); - - /// - /// Sets the client type associated with an application. - /// - /// The application. - /// The client type associated with the application. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetClientTypeAsync(TApplication application, string? type, CancellationToken cancellationToken); - - /// - /// Sets the consent type associated with an application. - /// - /// The application. - /// The consent type associated with the application. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetConsentTypeAsync(TApplication application, string? type, CancellationToken cancellationToken); - - /// - /// Sets the display name associated with an application. - /// - /// The application. - /// The display name associated with the application. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetDisplayNameAsync(TApplication application, string? name, CancellationToken cancellationToken); - - /// - /// Sets the localized display names associated with an application. - /// - /// The application. - /// The localized display names associated with the application. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetDisplayNamesAsync(TApplication application, - ImmutableDictionary names, CancellationToken cancellationToken); - - /// - /// Sets the permissions associated with an application. - /// - /// The application. - /// The permissions associated with the application - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetPermissionsAsync(TApplication application, ImmutableArray permissions, CancellationToken cancellationToken); - - /// - /// Sets the logout callback addresses associated with an application. - /// - /// The application. - /// The logout callback addresses associated with the application - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, - ImmutableArray addresses, CancellationToken cancellationToken); - - /// - /// Sets the additional properties associated with an application. - /// - /// The application. - /// The additional properties associated with the application. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetPropertiesAsync(TApplication application, - ImmutableDictionary properties, CancellationToken cancellationToken); - - /// - /// Sets the callback addresses associated with an application. - /// - /// The application. - /// The callback addresses associated with the application - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetRedirectUrisAsync(TApplication application, - ImmutableArray addresses, CancellationToken cancellationToken); - - /// - /// Sets the requirements associated with an application. - /// - /// The application. - /// The requirements associated with the application - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetRequirementsAsync(TApplication application, ImmutableArray requirements, CancellationToken cancellationToken); - - /// - /// Updates an existing application. - /// - /// The application to update. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken); - } -} \ No newline at end of file + /// Determines the number of applications that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of applications in the database. + /// + ValueTask CountAsync(CancellationToken cancellationToken); + + /// + /// Determines the number of applications that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of applications that match the specified query. + /// + ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken); + + /// + /// Creates a new application. + /// + /// The application to create. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Removes an existing application. + /// + /// The application to delete. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves an application using its unique identifier. + /// + /// The unique identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); + + /// + /// Retrieves an application using its client identifier. + /// + /// The client identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken); + + /// + /// Retrieves all the applications associated with the specified post_logout_redirect_uri. + /// + /// The post_logout_redirect_uri associated with the applications. + /// The that can be used to abort the operation. + /// The client applications corresponding to the specified post_logout_redirect_uri. + IAsyncEnumerable FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken); + + /// + /// Retrieves all the applications associated with the specified redirect_uri. + /// + /// The redirect_uri associated with the applications. + /// The that can be used to abort the operation. + /// The client applications corresponding to the specified redirect_uri. + IAsyncEnumerable FindByRedirectUriAsync(string address, CancellationToken cancellationToken); + + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken); + + /// + /// Retrieves the client identifier associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client identifier associated with the application. + /// + ValueTask GetClientIdAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the client secret associated with an application. + /// Note: depending on the manager used to create the application, + /// the client secret may be hashed for security reasons. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client secret associated with the application. + /// + ValueTask GetClientSecretAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the client type associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client type of the application (by default, "public"). + /// + ValueTask GetClientTypeAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the consent type associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the consent type of the application (by default, "explicit"). + /// + ValueTask GetConsentTypeAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the display name associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the application. + /// + ValueTask GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the localized display names associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the localized display names associated with the application. + /// + ValueTask> GetDisplayNamesAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the unique identifier associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the application. + /// + ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the permissions associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the permissions associated with the application. + /// + ValueTask> GetPermissionsAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the logout callback addresses associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the post_logout_redirect_uri associated with the application. + /// + ValueTask> GetPostLogoutRedirectUrisAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the additional properties associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the application. + /// + ValueTask> GetPropertiesAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the callback addresses associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the redirect_uri associated with the application. + /// + ValueTask> GetRedirectUrisAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Retrieves the requirements associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the requirements associated with the application. + /// + ValueTask> GetRequirementsAsync(TApplication application, CancellationToken cancellationToken); + + /// + /// Instantiates a new application. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the instantiated application, that can be persisted in the database. + /// + ValueTask InstantiateAsync(CancellationToken cancellationToken); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken); + + /// + /// Sets the client identifier associated with an application. + /// + /// The application. + /// The client identifier associated with the application. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetClientIdAsync(TApplication application, string? identifier, CancellationToken cancellationToken); + + /// + /// Sets the client secret associated with an application. + /// Note: depending on the manager used to create the application, + /// the client secret may be hashed for security reasons. + /// + /// The application. + /// The client secret associated with the application. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetClientSecretAsync(TApplication application, string? secret, CancellationToken cancellationToken); + + /// + /// Sets the client type associated with an application. + /// + /// The application. + /// The client type associated with the application. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetClientTypeAsync(TApplication application, string? type, CancellationToken cancellationToken); + + /// + /// Sets the consent type associated with an application. + /// + /// The application. + /// The consent type associated with the application. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetConsentTypeAsync(TApplication application, string? type, CancellationToken cancellationToken); + + /// + /// Sets the display name associated with an application. + /// + /// The application. + /// The display name associated with the application. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetDisplayNameAsync(TApplication application, string? name, CancellationToken cancellationToken); + + /// + /// Sets the localized display names associated with an application. + /// + /// The application. + /// The localized display names associated with the application. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetDisplayNamesAsync(TApplication application, + ImmutableDictionary names, CancellationToken cancellationToken); + + /// + /// Sets the permissions associated with an application. + /// + /// The application. + /// The permissions associated with the application + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetPermissionsAsync(TApplication application, ImmutableArray permissions, CancellationToken cancellationToken); + + /// + /// Sets the logout callback addresses associated with an application. + /// + /// The application. + /// The logout callback addresses associated with the application + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, + ImmutableArray addresses, CancellationToken cancellationToken); + + /// + /// Sets the additional properties associated with an application. + /// + /// The application. + /// The additional properties associated with the application. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetPropertiesAsync(TApplication application, + ImmutableDictionary properties, CancellationToken cancellationToken); + + /// + /// Sets the callback addresses associated with an application. + /// + /// The application. + /// The callback addresses associated with the application + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetRedirectUrisAsync(TApplication application, + ImmutableArray addresses, CancellationToken cancellationToken); + + /// + /// Sets the requirements associated with an application. + /// + /// The application. + /// The requirements associated with the application + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetRequirementsAsync(TApplication application, ImmutableArray requirements, CancellationToken cancellationToken); + + /// + /// Updates an existing application. + /// + /// The application to update. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken); +} diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs index 43ce30f6..613a1b67 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs @@ -12,349 +12,348 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to manage the authorizations stored in a database. +/// +/// The type of the Authorization entity. +public interface IOpenIddictAuthorizationStore where TAuthorization : class { /// - /// Provides methods allowing to manage the authorizations stored in a database. - /// - /// The type of the Authorization entity. - public interface IOpenIddictAuthorizationStore where TAuthorization : class - { - /// - /// Determines the number of authorizations that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of authorizations in the database. - /// - ValueTask CountAsync(CancellationToken cancellationToken); - - /// - /// Determines the number of authorizations that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of authorizations that match the specified query. - /// - ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken); - - /// - /// Creates a new authorization. - /// - /// The authorization to create. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Removes an existing authorization. - /// - /// The authorization to delete. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Retrieves the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken); - - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The minimal scopes associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken); - - /// - /// Retrieves the list of authorizations corresponding to the specified application identifier. - /// - /// The application identifier associated with the authorizations. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the specified application. - IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken); - - /// - /// Retrieves an authorization using its unique identifier. - /// - /// The unique identifier associated with the authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization corresponding to the identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); - - /// - /// Retrieves all the authorizations corresponding to the specified subject. - /// - /// The subject associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the specified subject. - IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken); - - /// - /// Retrieves the optional application identifier associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the application identifier associated with the authorization. - /// - ValueTask GetApplicationIdAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken); - - /// - /// Retrieves the creation date associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the creation date associated with the specified authorization. - /// - ValueTask GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Retrieves the unique identifier associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the authorization. - /// - ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Retrieves the additional properties associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the authorization. - /// - ValueTask> GetPropertiesAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Retrieves the scopes associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scopes associated with the specified authorization. - /// - ValueTask> GetScopesAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Retrieves the status associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the status associated with the specified authorization. - /// - ValueTask GetStatusAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Retrieves the subject associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the subject associated with the specified authorization. - /// - ValueTask GetSubjectAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Retrieves the type associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the type associated with the specified authorization. - /// - ValueTask GetTypeAsync(TAuthorization authorization, CancellationToken cancellationToken); - - /// - /// Instantiates a new authorization. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the instantiated authorization, that can be persisted in the database. - /// - ValueTask InstantiateAsync(CancellationToken cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken); - - /// - /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached. - /// Only authorizations created before the specified are removed. - /// - /// - /// To ensure ad-hoc authorizations that no longer have any valid/non-expired token - /// attached are correctly removed, the tokens should always be pruned first. - /// - /// The date before which authorizations are not pruned. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken); - - /// - /// Sets the application identifier associated with an authorization. - /// - /// The authorization. - /// The unique identifier associated with the client application. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetApplicationIdAsync(TAuthorization authorization, string? identifier, CancellationToken cancellationToken); - - /// - /// Sets the creation date associated with an authorization. - /// - /// The authorization. - /// The expiration date. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetCreationDateAsync(TAuthorization authorization, DateTimeOffset? date, CancellationToken cancellationToken); - - /// - /// Sets the additional properties associated with an authorization. - /// - /// The authorization. - /// The additional properties associated with the authorization. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetPropertiesAsync(TAuthorization authorization, - ImmutableDictionary properties, CancellationToken cancellationToken); - - /// - /// Sets the scopes associated with an authorization. - /// - /// The authorization. - /// The scopes associated with the authorization. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetScopesAsync(TAuthorization authorization, - ImmutableArray scopes, CancellationToken cancellationToken); - - /// - /// Sets the status associated with an authorization. - /// - /// The authorization. - /// The status associated with the authorization. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetStatusAsync(TAuthorization authorization, string? status, CancellationToken cancellationToken); - - /// - /// Sets the subject associated with an authorization. - /// - /// The authorization. - /// The subject associated with the authorization. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetSubjectAsync(TAuthorization authorization, string? subject, CancellationToken cancellationToken); - - /// - /// Sets the type associated with an authorization. - /// - /// The authorization. - /// The type associated with the authorization. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetTypeAsync(TAuthorization authorization, string? type, CancellationToken cancellationToken); - - /// - /// Updates an existing authorization. - /// - /// The authorization to update. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken); - } -} \ No newline at end of file + /// Determines the number of authorizations that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of authorizations in the database. + /// + ValueTask CountAsync(CancellationToken cancellationToken); + + /// + /// Determines the number of authorizations that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of authorizations that match the specified query. + /// + ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken); + + /// + /// Creates a new authorization. + /// + /// The authorization to create. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Removes an existing authorization. + /// + /// The authorization to delete. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Retrieves the authorizations corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the subject/client. + IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); + + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken); + + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken); + + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The minimal scopes associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, + ImmutableArray scopes, CancellationToken cancellationToken); + + /// + /// Retrieves the list of authorizations corresponding to the specified application identifier. + /// + /// The application identifier associated with the authorizations. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the specified application. + IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken); + + /// + /// Retrieves an authorization using its unique identifier. + /// + /// The unique identifier associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorization corresponding to the identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); + + /// + /// Retrieves all the authorizations corresponding to the specified subject. + /// + /// The subject associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the specified subject. + IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken); + + /// + /// Retrieves the optional application identifier associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the application identifier associated with the authorization. + /// + ValueTask GetApplicationIdAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken); + + /// + /// Retrieves the creation date associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the creation date associated with the specified authorization. + /// + ValueTask GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Retrieves the unique identifier associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the authorization. + /// + ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Retrieves the additional properties associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the authorization. + /// + ValueTask> GetPropertiesAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Retrieves the scopes associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scopes associated with the specified authorization. + /// + ValueTask> GetScopesAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Retrieves the status associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the status associated with the specified authorization. + /// + ValueTask GetStatusAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Retrieves the subject associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the subject associated with the specified authorization. + /// + ValueTask GetSubjectAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Retrieves the type associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the type associated with the specified authorization. + /// + ValueTask GetTypeAsync(TAuthorization authorization, CancellationToken cancellationToken); + + /// + /// Instantiates a new authorization. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the instantiated authorization, that can be persisted in the database. + /// + ValueTask InstantiateAsync(CancellationToken cancellationToken); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken); + + /// + /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached. + /// Only authorizations created before the specified are removed. + /// + /// + /// To ensure ad-hoc authorizations that no longer have any valid/non-expired token + /// attached are correctly removed, the tokens should always be pruned first. + /// + /// The date before which authorizations are not pruned. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken); + + /// + /// Sets the application identifier associated with an authorization. + /// + /// The authorization. + /// The unique identifier associated with the client application. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetApplicationIdAsync(TAuthorization authorization, string? identifier, CancellationToken cancellationToken); + + /// + /// Sets the creation date associated with an authorization. + /// + /// The authorization. + /// The expiration date. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetCreationDateAsync(TAuthorization authorization, DateTimeOffset? date, CancellationToken cancellationToken); + + /// + /// Sets the additional properties associated with an authorization. + /// + /// The authorization. + /// The additional properties associated with the authorization. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetPropertiesAsync(TAuthorization authorization, + ImmutableDictionary properties, CancellationToken cancellationToken); + + /// + /// Sets the scopes associated with an authorization. + /// + /// The authorization. + /// The scopes associated with the authorization. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetScopesAsync(TAuthorization authorization, + ImmutableArray scopes, CancellationToken cancellationToken); + + /// + /// Sets the status associated with an authorization. + /// + /// The authorization. + /// The status associated with the authorization. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetStatusAsync(TAuthorization authorization, string? status, CancellationToken cancellationToken); + + /// + /// Sets the subject associated with an authorization. + /// + /// The authorization. + /// The subject associated with the authorization. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetSubjectAsync(TAuthorization authorization, string? subject, CancellationToken cancellationToken); + + /// + /// Sets the type associated with an authorization. + /// + /// The authorization. + /// The type associated with the authorization. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetTypeAsync(TAuthorization authorization, string? type, CancellationToken cancellationToken); + + /// + /// Updates an existing authorization. + /// + /// The authorization to update. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken); +} diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs index 4f9f6ed7..688d728d 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs @@ -13,298 +13,297 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to manage the scopes stored in a database. +/// +/// The type of the Scope entity. +public interface IOpenIddictScopeStore where TScope : class { /// - /// Provides methods allowing to manage the scopes stored in a database. + /// Determines the number of scopes that exist in the database. /// - /// The type of the Scope entity. - public interface IOpenIddictScopeStore where TScope : class - { - /// - /// Determines the number of scopes that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of scopes in the database. - /// - ValueTask CountAsync(CancellationToken cancellationToken); + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of scopes in the database. + /// + ValueTask CountAsync(CancellationToken cancellationToken); - /// - /// Determines the number of scopes that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of scopes that match the specified query. - /// - ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken); + /// + /// Determines the number of scopes that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of scopes that match the specified query. + /// + ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken); - /// - /// Creates a new scope. - /// - /// The scope to create. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Creates a new scope. + /// + /// The scope to create. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Removes an existing scope. - /// - /// The scope to delete. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Removes an existing scope. + /// + /// The scope to delete. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Retrieves a scope using its unique identifier. - /// - /// The unique identifier associated with the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scope corresponding to the identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); + /// + /// Retrieves a scope using its unique identifier. + /// + /// The unique identifier associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); - /// - /// Retrieves a scope using its name. - /// - /// The name associated with the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scope corresponding to the specified name. - /// - ValueTask FindByNameAsync(string name, CancellationToken cancellationToken); + /// + /// Retrieves a scope using its name. + /// + /// The name associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the specified name. + /// + ValueTask FindByNameAsync(string name, CancellationToken cancellationToken); - /// - /// Retrieves a list of scopes using their name. - /// - /// The names associated with the scopes. - /// The that can be used to abort the operation. - /// The scopes corresponding to the specified names. - IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken); + /// + /// Retrieves a list of scopes using their name. + /// + /// The names associated with the scopes. + /// The that can be used to abort the operation. + /// The scopes corresponding to the specified names. + IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken); - /// - /// Retrieves all the scopes that contain the specified resource. - /// - /// The resource associated with the scopes. - /// The that can be used to abort the operation. - /// The scopes associated with the specified resource. - IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken); + /// + /// Retrieves all the scopes that contain the specified resource. + /// + /// The resource associated with the scopes. + /// The that can be used to abort the operation. + /// The scopes associated with the specified resource. + IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken); - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken); + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken); - /// - /// Retrieves the description associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the description associated with the specified scope. - /// - ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Retrieves the description associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the description associated with the specified scope. + /// + ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Retrieves the localized descriptions associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the localized descriptions associated with the specified scope. - /// - ValueTask> GetDescriptionsAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Retrieves the localized descriptions associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the localized descriptions associated with the specified scope. + /// + ValueTask> GetDescriptionsAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Retrieves the display name associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the display name associated with the scope. - /// - ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Retrieves the display name associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the scope. + /// + ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Retrieves the localized display names associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the localized display names associated with the scope. - /// - ValueTask> GetDisplayNamesAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Retrieves the localized display names associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the localized display names associated with the scope. + /// + ValueTask> GetDisplayNamesAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Retrieves the unique identifier associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the scope. - /// - ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Retrieves the unique identifier associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the scope. + /// + ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Retrieves the name associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the name associated with the specified scope. - /// - ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Retrieves the name associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the name associated with the specified scope. + /// + ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Retrieves the additional properties associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose - /// result returns all the additional properties associated with the scope. - /// - ValueTask> GetPropertiesAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Retrieves the additional properties associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose + /// result returns all the additional properties associated with the scope. + /// + ValueTask> GetPropertiesAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Retrieves the resources associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the resources associated with the scope. - /// - ValueTask> GetResourcesAsync(TScope scope, CancellationToken cancellationToken); + /// + /// Retrieves the resources associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the resources associated with the scope. + /// + ValueTask> GetResourcesAsync(TScope scope, CancellationToken cancellationToken); - /// - /// Instantiates a new scope. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the instantiated scope, that can be persisted in the database. - /// - ValueTask InstantiateAsync(CancellationToken cancellationToken); + /// + /// Instantiates a new scope. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the instantiated scope, that can be persisted in the database. + /// + ValueTask InstantiateAsync(CancellationToken cancellationToken); - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken); + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken); - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken); + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken); - /// - /// Sets the description associated with a scope. - /// - /// The scope. - /// The description associated with the authorization. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetDescriptionAsync(TScope scope, string? description, CancellationToken cancellationToken); + /// + /// Sets the description associated with a scope. + /// + /// The scope. + /// The description associated with the authorization. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetDescriptionAsync(TScope scope, string? description, CancellationToken cancellationToken); - /// - /// Sets the localized descriptions associated with a scope. - /// - /// The scope. - /// The localized descriptions associated with the authorization. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetDescriptionsAsync(TScope scope, - ImmutableDictionary descriptions, CancellationToken cancellationToken); + /// + /// Sets the localized descriptions associated with a scope. + /// + /// The scope. + /// The localized descriptions associated with the authorization. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetDescriptionsAsync(TScope scope, + ImmutableDictionary descriptions, CancellationToken cancellationToken); - /// - /// Sets the display name associated with a scope. - /// - /// The scope. - /// The display name associated with the scope. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetDisplayNameAsync(TScope scope, string? name, CancellationToken cancellationToken); + /// + /// Sets the display name associated with a scope. + /// + /// The scope. + /// The display name associated with the scope. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetDisplayNameAsync(TScope scope, string? name, CancellationToken cancellationToken); - /// - /// Sets the localized display names associated with a scope. - /// - /// The scope. - /// The localized display names associated with the scope. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetDisplayNamesAsync(TScope scope, - ImmutableDictionary names, CancellationToken cancellationToken); + /// + /// Sets the localized display names associated with a scope. + /// + /// The scope. + /// The localized display names associated with the scope. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetDisplayNamesAsync(TScope scope, + ImmutableDictionary names, CancellationToken cancellationToken); - /// - /// Sets the name associated with a scope. - /// - /// The scope. - /// The name associated with the authorization. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetNameAsync(TScope scope, string? name, CancellationToken cancellationToken); + /// + /// Sets the name associated with a scope. + /// + /// The scope. + /// The name associated with the authorization. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetNameAsync(TScope scope, string? name, CancellationToken cancellationToken); - /// - /// Sets the additional properties associated with a scope. - /// - /// The scope. - /// The additional properties associated with the scope. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetPropertiesAsync(TScope scope, - ImmutableDictionary properties, CancellationToken cancellationToken); + /// + /// Sets the additional properties associated with a scope. + /// + /// The scope. + /// The additional properties associated with the scope. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetPropertiesAsync(TScope scope, + ImmutableDictionary properties, CancellationToken cancellationToken); - /// - /// Sets the resources associated with a scope. - /// - /// The scope. - /// The resources associated with the scope. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetResourcesAsync(TScope scope, ImmutableArray resources, CancellationToken cancellationToken); + /// + /// Sets the resources associated with a scope. + /// + /// The scope. + /// The resources associated with the scope. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetResourcesAsync(TScope scope, ImmutableArray resources, CancellationToken cancellationToken); - /// - /// Updates an existing scope. - /// - /// The scope to update. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken); - } -} \ No newline at end of file + /// + /// Updates an existing scope. + /// + /// The scope to update. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken); +} diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs index c8e068e3..4a747f8d 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs @@ -12,433 +12,432 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -namespace OpenIddict.Abstractions +namespace OpenIddict.Abstractions; + +/// +/// Provides methods allowing to manage the tokens stored in a database. +/// +/// The type of the Token entity. +public interface IOpenIddictTokenStore where TToken : class { /// - /// Provides methods allowing to manage the tokens stored in a database. - /// - /// The type of the Token entity. - public interface IOpenIddictTokenStore where TToken : class - { - /// - /// Determines the number of tokens that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of applications in the database. - /// - ValueTask CountAsync(CancellationToken cancellationToken); - - /// - /// Determines the number of tokens that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of tokens that match the specified query. - /// - ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken); - - /// - /// Creates a new token. - /// - /// The token to create. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask CreateAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Removes a token. - /// - /// The token to delete. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The tokens corresponding to the subject/client. - IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); - - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken); - - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken); - - /// - /// Retrieves the list of tokens corresponding to the specified application identifier. - /// - /// The application identifier associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified application. - IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken); - - /// - /// Retrieves the list of tokens corresponding to the specified authorization identifier. - /// - /// The authorization identifier associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified authorization. - IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken); - - /// - /// Retrieves a token using its unique identifier. - /// - /// The unique identifier associated with the token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the token corresponding to the unique identifier. - /// - ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); - - /// - /// Retrieves the list of tokens corresponding to the specified reference identifier. - /// Note: the reference identifier may be hashed or encrypted for security reasons. - /// - /// The reference identifier associated with the tokens. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the tokens corresponding to the specified reference identifier. - /// - ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken); - - /// - /// Retrieves the list of tokens corresponding to the specified subject. - /// - /// The subject associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified subject. - IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken); - - /// - /// Retrieves the optional application identifier associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the application identifier associated with the token. - /// - ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken); - - /// - /// Retrieves the optional authorization identifier associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization identifier associated with the token. - /// - ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the creation date associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the creation date associated with the specified token. - /// - ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the expiration date associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the expiration date associated with the specified token. - /// - ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the unique identifier associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the token. - /// - ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the payload associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the payload associated with the specified token. - /// - ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the additional properties associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the token. - /// - ValueTask> GetPropertiesAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the redemption date associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the redemption date associated with the specified token. - /// - ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the reference identifier associated with a token. - /// Note: depending on the manager used to create the token, - /// the reference identifier may be hashed for security reasons. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the reference identifier associated with the specified token. - /// - ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the status associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the status associated with the specified token. - /// - ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the subject associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the subject associated with the specified token. - /// - ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Retrieves the token type associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the token type associated with the specified token. - /// - ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken); - - /// - /// Instantiates a new token. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the instantiated token, that can be persisted in the database. - /// - ValueTask InstantiateAsync(CancellationToken cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken); - - /// - /// Removes the tokens that are marked as invalid or whose attached authorization is no longer valid. - /// Only tokens created before the specified are removed. - /// - /// The date before which tokens are not pruned. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken); - - /// - /// Sets the application identifier associated with a token. - /// - /// The token. - /// The unique identifier associated with the token. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken); - - /// - /// Sets the authorization identifier associated with a token. - /// - /// The token. - /// The unique identifier associated with the token. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetAuthorizationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken); - - /// - /// Sets the creation date associated with a token. - /// - /// The token. - /// The creation date. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetCreationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken); - - /// - /// Sets the expiration date associated with a token. - /// - /// The token. - /// The expiration date. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetExpirationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken); - - /// - /// Sets the payload associated with a token. - /// - /// The token. - /// The payload associated with the token. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetPayloadAsync(TToken token, string? payload, CancellationToken cancellationToken); - - /// - /// Sets the additional properties associated with a token. - /// - /// The token. - /// The additional properties associated with the token. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetPropertiesAsync(TToken token, - ImmutableDictionary properties, CancellationToken cancellationToken); - - /// - /// Sets the redemption date associated with a token. - /// - /// The token. - /// The redemption date. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetRedemptionDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken); - - /// - /// Sets the reference identifier associated with a token. - /// Note: depending on the manager used to create the token, - /// the reference identifier may be hashed for security reasons. - /// - /// The token. - /// The reference identifier associated with the token. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetReferenceIdAsync(TToken token, string? identifier, CancellationToken cancellationToken); - - /// - /// Sets the status associated with a token. - /// - /// The token. - /// The status associated with the authorization. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetStatusAsync(TToken token, string? status, CancellationToken cancellationToken); - - /// - /// Sets the subject associated with a token. - /// - /// The token. - /// The subject associated with the token. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetSubjectAsync(TToken token, string? subject, CancellationToken cancellationToken); - - /// - /// Sets the token type associated with a token. - /// - /// The token. - /// The token type associated with the token. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask SetTypeAsync(TToken token, string? type, CancellationToken cancellationToken); - - /// - /// Updates an existing token. - /// - /// The token to update. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken); - } -} \ No newline at end of file + /// Determines the number of tokens that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of applications in the database. + /// + ValueTask CountAsync(CancellationToken cancellationToken); + + /// + /// Determines the number of tokens that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of tokens that match the specified query. + /// + ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken); + + /// + /// Creates a new token. + /// + /// The token to create. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask CreateAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Removes a token. + /// + /// The token to delete. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the tokens corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The that can be used to abort the operation. + /// The tokens corresponding to the subject/client. + IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken); + + /// + /// Retrieves the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The that can be used to abort the operation. + /// The tokens corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken); + + /// + /// Retrieves the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The token type. + /// The that can be used to abort the operation. + /// The tokens corresponding to the criteria. + IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken); + + /// + /// Retrieves the list of tokens corresponding to the specified application identifier. + /// + /// The application identifier associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified application. + IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken); + + /// + /// Retrieves the list of tokens corresponding to the specified authorization identifier. + /// + /// The authorization identifier associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified authorization. + IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken); + + /// + /// Retrieves a token using its unique identifier. + /// + /// The unique identifier associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token corresponding to the unique identifier. + /// + ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken); + + /// + /// Retrieves the list of tokens corresponding to the specified reference identifier. + /// Note: the reference identifier may be hashed or encrypted for security reasons. + /// + /// The reference identifier associated with the tokens. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens corresponding to the specified reference identifier. + /// + ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken); + + /// + /// Retrieves the list of tokens corresponding to the specified subject. + /// + /// The subject associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified subject. + IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken); + + /// + /// Retrieves the optional application identifier associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the application identifier associated with the token. + /// + ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken); + + /// + /// Retrieves the optional authorization identifier associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorization identifier associated with the token. + /// + ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the creation date associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the creation date associated with the specified token. + /// + ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the expiration date associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the expiration date associated with the specified token. + /// + ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the unique identifier associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the payload associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the payload associated with the specified token. + /// + ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the additional properties associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the token. + /// + ValueTask> GetPropertiesAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the redemption date associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the redemption date associated with the specified token. + /// + ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the reference identifier associated with a token. + /// Note: depending on the manager used to create the token, + /// the reference identifier may be hashed for security reasons. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the reference identifier associated with the specified token. + /// + ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the status associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the status associated with the specified token. + /// + ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the subject associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the subject associated with the specified token. + /// + ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Retrieves the token type associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token type associated with the specified token. + /// + ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken); + + /// + /// Instantiates a new token. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the instantiated token, that can be persisted in the database. + /// + ValueTask InstantiateAsync(CancellationToken cancellationToken); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken); + + /// + /// Removes the tokens that are marked as invalid or whose attached authorization is no longer valid. + /// Only tokens created before the specified are removed. + /// + /// The date before which tokens are not pruned. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken); + + /// + /// Sets the application identifier associated with a token. + /// + /// The token. + /// The unique identifier associated with the token. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken); + + /// + /// Sets the authorization identifier associated with a token. + /// + /// The token. + /// The unique identifier associated with the token. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetAuthorizationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken); + + /// + /// Sets the creation date associated with a token. + /// + /// The token. + /// The creation date. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetCreationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken); + + /// + /// Sets the expiration date associated with a token. + /// + /// The token. + /// The expiration date. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetExpirationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken); + + /// + /// Sets the payload associated with a token. + /// + /// The token. + /// The payload associated with the token. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetPayloadAsync(TToken token, string? payload, CancellationToken cancellationToken); + + /// + /// Sets the additional properties associated with a token. + /// + /// The token. + /// The additional properties associated with the token. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetPropertiesAsync(TToken token, + ImmutableDictionary properties, CancellationToken cancellationToken); + + /// + /// Sets the redemption date associated with a token. + /// + /// The token. + /// The redemption date. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetRedemptionDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken); + + /// + /// Sets the reference identifier associated with a token. + /// Note: depending on the manager used to create the token, + /// the reference identifier may be hashed for security reasons. + /// + /// The token. + /// The reference identifier associated with the token. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetReferenceIdAsync(TToken token, string? identifier, CancellationToken cancellationToken); + + /// + /// Sets the status associated with a token. + /// + /// The token. + /// The status associated with the authorization. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetStatusAsync(TToken token, string? status, CancellationToken cancellationToken); + + /// + /// Sets the subject associated with a token. + /// + /// The token. + /// The subject associated with the token. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetSubjectAsync(TToken token, string? subject, CancellationToken cancellationToken); + + /// + /// Sets the token type associated with a token. + /// + /// The token. + /// The token type associated with the token. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask SetTypeAsync(TToken token, string? type, CancellationToken cancellationToken); + + /// + /// Updates an existing token. + /// + /// The token to update. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken); +} diff --git a/src/OpenIddict.Core/Caches/OpenIddictApplicationCache.cs b/src/OpenIddict.Core/Caches/OpenIddictApplicationCache.cs index fd1f8cb1..5e6addbb 100644 --- a/src/OpenIddict.Core/Caches/OpenIddictApplicationCache.cs +++ b/src/OpenIddict.Core/Caches/OpenIddictApplicationCache.cs @@ -17,354 +17,353 @@ using Microsoft.Extensions.Primitives; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Provides methods allowing to cache applications after retrieving them from the store. +/// +/// The type of the Application entity. +public class OpenIddictApplicationCache : IOpenIddictApplicationCache, IDisposable where TApplication : class { - /// - /// Provides methods allowing to cache applications after retrieving them from the store. - /// - /// The type of the Application entity. - public class OpenIddictApplicationCache : IOpenIddictApplicationCache, IDisposable where TApplication : class - { - private readonly MemoryCache _cache; - private readonly ConcurrentDictionary _signals; - private readonly IOpenIddictApplicationStore _store; + private readonly MemoryCache _cache; + private readonly ConcurrentDictionary _signals; + private readonly IOpenIddictApplicationStore _store; - public OpenIddictApplicationCache( - IOptionsMonitor options, - IOpenIddictApplicationStoreResolver resolver) + public OpenIddictApplicationCache( + IOptionsMonitor options, + IOpenIddictApplicationStoreResolver resolver) + { + _cache = new MemoryCache(new MemoryCacheOptions { - _cache = new MemoryCache(new MemoryCacheOptions - { - SizeLimit = options.CurrentValue.EntityCacheLimit - }); + SizeLimit = options.CurrentValue.EntityCacheLimit + }); - _signals = new ConcurrentDictionary(StringComparer.Ordinal); - _store = resolver.Get(); + _signals = new ConcurrentDictionary(StringComparer.Ordinal); + _store = resolver.Get(); + } + + /// + public async ValueTask AddAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public async ValueTask AddAsync(TApplication application, CancellationToken cancellationToken) + _cache.Remove(new { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Method = nameof(FindByClientIdAsync), + Identifier = await _store.GetClientIdAsync(application, cancellationToken) + }); + + _cache.Remove(new + { + Method = nameof(FindByIdAsync), + Identifier = await _store.GetIdAsync(application, cancellationToken) + }); + foreach (var address in await _store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) + { _cache.Remove(new { - Method = nameof(FindByClientIdAsync), - Identifier = await _store.GetClientIdAsync(application, cancellationToken) + Method = nameof(FindByPostLogoutRedirectUriAsync), + Address = address }); + } + foreach (var address in await _store.GetRedirectUrisAsync(application, cancellationToken)) + { _cache.Remove(new { - Method = nameof(FindByIdAsync), - Identifier = await _store.GetIdAsync(application, cancellationToken) + Method = nameof(FindByRedirectUriAsync), + Address = address }); + } - foreach (var address in await _store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) - { - _cache.Remove(new - { - Method = nameof(FindByPostLogoutRedirectUriAsync), - Address = address - }); - } + await CreateEntryAsync(new + { + Method = nameof(FindByIdAsync), + Identifier = await _store.GetIdAsync(application, cancellationToken) + }, application, cancellationToken); - foreach (var address in await _store.GetRedirectUrisAsync(application, cancellationToken)) - { - _cache.Remove(new - { - Method = nameof(FindByRedirectUriAsync), - Address = address - }); - } + await CreateEntryAsync(new + { + Method = nameof(FindByClientIdAsync), + Identifier = await _store.GetClientIdAsync(application, cancellationToken) + }, application, cancellationToken); + } - await CreateEntryAsync(new - { - Method = nameof(FindByIdAsync), - Identifier = await _store.GetIdAsync(application, cancellationToken) - }, application, cancellationToken); + /// + public void Dispose() + { + foreach (var signal in _signals) + { + signal.Value.Dispose(); + } - await CreateEntryAsync(new - { - Method = nameof(FindByClientIdAsync), - Identifier = await _store.GetClientIdAsync(application, cancellationToken) - }, application, cancellationToken); + _cache.Dispose(); + } + + /// + public ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public void Dispose() + var parameters = new { - foreach (var signal in _signals) - { - signal.Value.Dispose(); - } + Method = nameof(FindByClientIdAsync), + Identifier = identifier + }; - _cache.Dispose(); + if (_cache.TryGetValue(parameters, out TApplication? application)) + { + return new ValueTask(application); } - /// - public ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken) + return new ValueTask(ExecuteAsync()); + + async Task ExecuteAsync() { - if (string.IsNullOrEmpty(identifier)) + if ((application = await _store.FindByClientIdAsync(identifier, cancellationToken)) is not null) { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + await AddAsync(application, cancellationToken); } - var parameters = new - { - Method = nameof(FindByClientIdAsync), - Identifier = identifier - }; - - if (_cache.TryGetValue(parameters, out TApplication? application)) - { - return new ValueTask(application); - } + await CreateEntryAsync(parameters, application, cancellationToken); - return new ValueTask(ExecuteAsync()); + return application; + } + } - async Task ExecuteAsync() - { - if ((application = await _store.FindByClientIdAsync(identifier, cancellationToken)) is not null) - { - await AddAsync(application, cancellationToken); - } + /// + public ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - await CreateEntryAsync(parameters, application, cancellationToken); + var parameters = new + { + Method = nameof(FindByIdAsync), + Identifier = identifier + }; - return application; - } + if (_cache.TryGetValue(parameters, out TApplication? application)) + { + return new ValueTask(application); } - /// - public ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + return new ValueTask(ExecuteAsync()); + + async Task ExecuteAsync() { - if (string.IsNullOrEmpty(identifier)) + if ((application = await _store.FindByIdAsync(identifier, cancellationToken)) is not null) { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + await AddAsync(application, cancellationToken); } + await CreateEntryAsync(parameters, application, cancellationToken); + + return application; + } + } + + /// + public IAsyncEnumerable FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(address)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } + + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var parameters = new { - Method = nameof(FindByIdAsync), - Identifier = identifier + Method = nameof(FindByPostLogoutRedirectUriAsync), + Address = address }; - if (_cache.TryGetValue(parameters, out TApplication? application)) + if (!_cache.TryGetValue(parameters, out ImmutableArray applications)) { - return new ValueTask(application); - } - - return new ValueTask(ExecuteAsync()); + var builder = ImmutableArray.CreateBuilder(); - async Task ExecuteAsync() - { - if ((application = await _store.FindByIdAsync(identifier, cancellationToken)) is not null) + await foreach (var application in _store.FindByPostLogoutRedirectUriAsync(address, cancellationToken)) { + builder.Add(application); + await AddAsync(application, cancellationToken); } - await CreateEntryAsync(parameters, application, cancellationToken); + applications = builder.ToImmutable(); - return application; + await CreateEntryAsync(parameters, applications, cancellationToken); } - } - /// - public IAsyncEnumerable FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(address)) + foreach (var application in applications) { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + yield return application; } + } + } - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindByPostLogoutRedirectUriAsync), - Address = address - }; - - if (!_cache.TryGetValue(parameters, out ImmutableArray applications)) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var application in _store.FindByPostLogoutRedirectUriAsync(address, cancellationToken)) - { - builder.Add(application); - - await AddAsync(application, cancellationToken); - } - - applications = builder.ToImmutable(); - - await CreateEntryAsync(parameters, applications, cancellationToken); - } - - foreach (var application in applications) - { - yield return application; - } - } + /// + public IAsyncEnumerable FindByRedirectUriAsync(string address, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(address)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); } - /// - public IAsyncEnumerable FindByRedirectUriAsync(string address, CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(address)) + var parameters = new { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } - - return ExecuteAsync(cancellationToken); + Method = nameof(FindByRedirectUriAsync), + Address = address + }; - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + if (!_cache.TryGetValue(parameters, out ImmutableArray applications)) { - var parameters = new - { - Method = nameof(FindByRedirectUriAsync), - Address = address - }; + var builder = ImmutableArray.CreateBuilder(); - if (!_cache.TryGetValue(parameters, out ImmutableArray applications)) + await foreach (var application in _store.FindByRedirectUriAsync(address, cancellationToken)) { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var application in _store.FindByRedirectUriAsync(address, cancellationToken)) - { - builder.Add(application); + builder.Add(application); - await AddAsync(application, cancellationToken); - } + await AddAsync(application, cancellationToken); + } - applications = builder.ToImmutable(); + applications = builder.ToImmutable(); - await CreateEntryAsync(parameters, applications, cancellationToken); - } + await CreateEntryAsync(parameters, applications, cancellationToken); + } - foreach (var application in applications) - { - yield return application; - } + foreach (var application in applications) + { + yield return application; } } + } - /// - public async ValueTask RemoveAsync(TApplication application, CancellationToken cancellationToken) + /// + public async ValueTask RemoveAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - var identifier = await _store.GetIdAsync(application, cancellationToken); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0196)); - } + var identifier = await _store.GetIdAsync(application, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0196)); + } - if (_signals.TryRemove(identifier, out CancellationTokenSource? signal)) - { - signal.Cancel(); - signal.Dispose(); - } + if (_signals.TryRemove(identifier, out CancellationTokenSource? signal)) + { + signal.Cancel(); + signal.Dispose(); } + } - /// - /// Creates a cache entry for the specified key. - /// - /// The cache key. - /// The application to store in the cache entry, if applicable. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - protected virtual async ValueTask CreateEntryAsync(object key, TApplication? application, CancellationToken cancellationToken) + /// + /// Creates a cache entry for the specified key. + /// + /// The cache key. + /// The application to store in the cache entry, if applicable. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + protected virtual async ValueTask CreateEntryAsync(object key, TApplication? application, CancellationToken cancellationToken) + { + if (key is null) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } + throw new ArgumentNullException(nameof(key)); + } - using var entry = _cache.CreateEntry(key); + using var entry = _cache.CreateEntry(key); - if (application is not null) + if (application is not null) + { + var signal = await CreateExpirationSignalAsync(application, cancellationToken); + if (signal is null) { - var signal = await CreateExpirationSignalAsync(application, cancellationToken); - if (signal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); - } - - entry.AddExpirationToken(signal); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); } - entry.SetSize(1L); - entry.SetValue(application); + entry.AddExpirationToken(signal); } - /// - /// Creates a cache entry for the specified key. - /// - /// The cache key. - /// The applications to store in the cache entry. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - protected virtual async ValueTask CreateEntryAsync( - object key, ImmutableArray applications, CancellationToken cancellationToken) + entry.SetSize(1L); + entry.SetValue(application); + } + + /// + /// Creates a cache entry for the specified key. + /// + /// The cache key. + /// The applications to store in the cache entry. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + protected virtual async ValueTask CreateEntryAsync( + object key, ImmutableArray applications, CancellationToken cancellationToken) + { + if (key is null) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } + throw new ArgumentNullException(nameof(key)); + } - using var entry = _cache.CreateEntry(key); + using var entry = _cache.CreateEntry(key); - foreach (var application in applications) + foreach (var application in applications) + { + var signal = await CreateExpirationSignalAsync(application, cancellationToken); + if (signal is null) { - var signal = await CreateExpirationSignalAsync(application, cancellationToken); - if (signal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); - } - - entry.AddExpirationToken(signal); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); } - entry.SetSize(applications.Length); - entry.SetValue(applications); + entry.AddExpirationToken(signal); } - /// - /// Creates an expiration signal allowing to invalidate all the - /// cache entries associated with the specified application. - /// - /// The application associated with the expiration signal. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns an expiration signal for the specified application. - /// - protected virtual async ValueTask CreateExpirationSignalAsync( - TApplication application, CancellationToken cancellationToken) + entry.SetSize(applications.Length); + entry.SetValue(applications); + } + + /// + /// Creates an expiration signal allowing to invalidate all the + /// cache entries associated with the specified application. + /// + /// The application associated with the expiration signal. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns an expiration signal for the specified application. + /// + protected virtual async ValueTask CreateExpirationSignalAsync( + TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - var identifier = await _store.GetIdAsync(application, cancellationToken); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0196)); - } + var identifier = await _store.GetIdAsync(application, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0196)); + } - var signal = _signals.GetOrAdd(identifier, _ => new CancellationTokenSource()); + var signal = _signals.GetOrAdd(identifier, _ => new CancellationTokenSource()); - return new CancellationChangeToken(signal.Token); - } + return new CancellationChangeToken(signal.Token); } } diff --git a/src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs b/src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs index 8341d3d7..e35ff7a6 100644 --- a/src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs +++ b/src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs @@ -17,530 +17,529 @@ using Microsoft.Extensions.Primitives; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Provides methods allowing to cache authorizations after retrieving them from the store. +/// +/// The type of the Authorization entity. +public class OpenIddictAuthorizationCache : IOpenIddictAuthorizationCache, IDisposable where TAuthorization : class { - /// - /// Provides methods allowing to cache authorizations after retrieving them from the store. - /// - /// The type of the Authorization entity. - public class OpenIddictAuthorizationCache : IOpenIddictAuthorizationCache, IDisposable where TAuthorization : class - { - private readonly MemoryCache _cache; - private readonly ConcurrentDictionary _signals; - private readonly IOpenIddictAuthorizationStore _store; + private readonly MemoryCache _cache; + private readonly ConcurrentDictionary _signals; + private readonly IOpenIddictAuthorizationStore _store; - public OpenIddictAuthorizationCache( - IOptionsMonitor options, - IOpenIddictAuthorizationStoreResolver resolver) + public OpenIddictAuthorizationCache( + IOptionsMonitor options, + IOpenIddictAuthorizationStoreResolver resolver) + { + _cache = new MemoryCache(new MemoryCacheOptions { - _cache = new MemoryCache(new MemoryCacheOptions - { - SizeLimit = options.CurrentValue.EntityCacheLimit - }); + SizeLimit = options.CurrentValue.EntityCacheLimit + }); - _signals = new ConcurrentDictionary(StringComparer.Ordinal); - _store = resolver.Get(); - } + _signals = new ConcurrentDictionary(StringComparer.Ordinal); + _store = resolver.Get(); + } - /// - public async ValueTask AddAsync(TAuthorization authorization, CancellationToken cancellationToken) + /// + public async ValueTask AddAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(authorization, cancellationToken), - Client = await _store.GetApplicationIdAsync(authorization, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindAsync), + Subject = await _store.GetSubjectAsync(authorization, cancellationToken), + Client = await _store.GetApplicationIdAsync(authorization, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(authorization, cancellationToken), - Client = await _store.GetApplicationIdAsync(authorization, cancellationToken), - Status = await _store.GetStatusAsync(authorization, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindAsync), + Subject = await _store.GetSubjectAsync(authorization, cancellationToken), + Client = await _store.GetApplicationIdAsync(authorization, cancellationToken), + Status = await _store.GetStatusAsync(authorization, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(authorization, cancellationToken), - Client = await _store.GetApplicationIdAsync(authorization, cancellationToken), - Status = await _store.GetStatusAsync(authorization, cancellationToken), - Type = await _store.GetTypeAsync(authorization, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindAsync), + Subject = await _store.GetSubjectAsync(authorization, cancellationToken), + Client = await _store.GetApplicationIdAsync(authorization, cancellationToken), + Status = await _store.GetStatusAsync(authorization, cancellationToken), + Type = await _store.GetTypeAsync(authorization, cancellationToken) + }); + + _cache.Remove(new + { + Method = nameof(FindByApplicationIdAsync), + Identifier = await _store.GetApplicationIdAsync(authorization, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindByApplicationIdAsync), - Identifier = await _store.GetApplicationIdAsync(authorization, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindByIdAsync), + Identifier = await _store.GetIdAsync(authorization, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindByIdAsync), - Identifier = await _store.GetIdAsync(authorization, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindBySubjectAsync), + Subject = await _store.GetSubjectAsync(authorization, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindBySubjectAsync), - Subject = await _store.GetSubjectAsync(authorization, cancellationToken) - }); + await CreateEntryAsync(new + { + Method = nameof(FindByIdAsync), + Identifier = await _store.GetIdAsync(authorization, cancellationToken) + }, authorization, cancellationToken); + } - await CreateEntryAsync(new - { - Method = nameof(FindByIdAsync), - Identifier = await _store.GetIdAsync(authorization, cancellationToken) - }, authorization, cancellationToken); + /// + public void Dispose() + { + foreach (var signal in _signals) + { + signal.Value.Dispose(); } - /// - public void Dispose() + _cache.Dispose(); + } + + /// + public IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) { - foreach (var signal in _signals) - { - signal.Value.Dispose(); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - _cache.Dispose(); + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); } - /// - public IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) + var parameters = new { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + Method = nameof(FindAsync), + Subject = subject, + Client = client + }; - if (string.IsNullOrEmpty(client)) + if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - return ExecuteAsync(cancellationToken); + var builder = ImmutableArray.CreateBuilder(); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new + await foreach (var authorization in _store.FindAsync(subject, client, cancellationToken)) { - Method = nameof(FindAsync), - Subject = subject, - Client = client - }; + builder.Add(authorization); - if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) - { - var builder = ImmutableArray.CreateBuilder(); + await AddAsync(authorization, cancellationToken); + } - await foreach (var authorization in _store.FindAsync(subject, client, cancellationToken)) - { - builder.Add(authorization); + authorizations = builder.ToImmutable(); - await AddAsync(authorization, cancellationToken); - } + await CreateEntryAsync(parameters, authorizations, cancellationToken); + } - authorizations = builder.ToImmutable(); + foreach (var authorization in authorizations) + { + yield return authorization; + } + } + } - await CreateEntryAsync(parameters, authorizations, cancellationToken); - } + /// + public IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - foreach (var authorization in authorizations) - { - yield return authorization; - } - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); } - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(status)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + return ExecuteAsync(cancellationToken); - if (string.IsNullOrEmpty(status)) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var parameters = new { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - return ExecuteAsync(cancellationToken); + Method = nameof(FindAsync), + Subject = subject, + Client = client, + Status = status + }; - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client, - Status = status - }; + var builder = ImmutableArray.CreateBuilder(); - if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) + await foreach (var authorization in _store.FindAsync(subject, client, status, cancellationToken)) { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var authorization in _store.FindAsync(subject, client, status, cancellationToken)) - { - builder.Add(authorization); + builder.Add(authorization); - await AddAsync(authorization, cancellationToken); - } - - authorizations = builder.ToImmutable(); - - await CreateEntryAsync(parameters, authorizations, cancellationToken); + await AddAsync(authorization, cancellationToken); } - foreach (var authorization in authorizations) - { - yield return authorization; - } - } - } + authorizations = builder.ToImmutable(); - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + await CreateEntryAsync(parameters, authorizations, cancellationToken); } - if (string.IsNullOrEmpty(type)) + foreach (var authorization in authorizations) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + yield return authorization; } + } + } - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client, - Status = status, - Type = type - }; - - if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var authorization in _store.FindAsync(subject, client, status, type, cancellationToken)) - { - builder.Add(authorization); - - await AddAsync(authorization, cancellationToken); - } - - authorizations = builder.ToImmutable(); + /// + public IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - await CreateEntryAsync(parameters, authorizations, cancellationToken); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - foreach (var authorization in authorizations) - { - yield return authorization; - } - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); } - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(type)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + return ExecuteAsync(cancellationToken); - if (string.IsNullOrEmpty(status)) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var parameters = new { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + Method = nameof(FindAsync), + Subject = subject, + Client = client, + Status = status, + Type = type + }; - if (string.IsNullOrEmpty(type)) + if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + var builder = ImmutableArray.CreateBuilder(); - // Note: this method is only partially cached. - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - await foreach (var authorization in _store.FindAsync(subject, client, status, type, scopes, cancellationToken)) + await foreach (var authorization in _store.FindAsync(subject, client, status, type, cancellationToken)) { - await AddAsync(authorization, cancellationToken); + builder.Add(authorization); - yield return authorization; + await AddAsync(authorization, cancellationToken); } + + authorizations = builder.ToImmutable(); + + await CreateEntryAsync(parameters, authorizations, cancellationToken); } - } - /// - public IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) + foreach (var authorization in authorizations) { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + yield return authorization; } + } + } - return ExecuteAsync(cancellationToken); + /// + public IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, + ImmutableArray scopes, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindByApplicationIdAsync), - Identifier = identifier - }; + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) - { - var builder = ImmutableArray.CreateBuilder(); + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - await foreach (var authorization in _store.FindByApplicationIdAsync(identifier, cancellationToken)) - { - builder.Add(authorization); + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - await AddAsync(authorization, cancellationToken); - } + // Note: this method is only partially cached. - authorizations = builder.ToImmutable(); + return ExecuteAsync(cancellationToken); - await CreateEntryAsync(parameters, authorizations, cancellationToken); - } + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var authorization in _store.FindAsync(subject, client, status, type, scopes, cancellationToken)) + { + await AddAsync(authorization, cancellationToken); - foreach (var authorization in authorizations) - { - yield return authorization; - } + yield return authorization; } } + } - /// - public ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + /// + public IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var parameters = new { - Method = nameof(FindByIdAsync), + Method = nameof(FindByApplicationIdAsync), Identifier = identifier }; - if (_cache.TryGetValue(parameters, out TAuthorization? authorization)) + if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) { - return new ValueTask(authorization); - } + var builder = ImmutableArray.CreateBuilder(); - return new ValueTask(ExecuteAsync()); - - async Task ExecuteAsync() - { - if ((authorization = await _store.FindByIdAsync(identifier, cancellationToken)) is not null) + await foreach (var authorization in _store.FindByApplicationIdAsync(identifier, cancellationToken)) { + builder.Add(authorization); + await AddAsync(authorization, cancellationToken); } - await CreateEntryAsync(parameters, authorization, cancellationToken); + authorizations = builder.ToImmutable(); - return authorization; + await CreateEntryAsync(parameters, authorizations, cancellationToken); } - } - /// - public IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) + foreach (var authorization in authorizations) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + yield return authorization; } + } + } - return ExecuteAsync(cancellationToken); + /// + public ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindBySubjectAsync), - Subject = subject - }; + var parameters = new + { + Method = nameof(FindByIdAsync), + Identifier = identifier + }; - if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) - { - var builder = ImmutableArray.CreateBuilder(); + if (_cache.TryGetValue(parameters, out TAuthorization? authorization)) + { + return new ValueTask(authorization); + } - await foreach (var authorization in _store.FindBySubjectAsync(subject, cancellationToken)) - { - builder.Add(authorization); + return new ValueTask(ExecuteAsync()); - await AddAsync(authorization, cancellationToken); - } + async Task ExecuteAsync() + { + if ((authorization = await _store.FindByIdAsync(identifier, cancellationToken)) is not null) + { + await AddAsync(authorization, cancellationToken); + } - authorizations = builder.ToImmutable(); + await CreateEntryAsync(parameters, authorization, cancellationToken); - await CreateEntryAsync(parameters, authorizations, cancellationToken); - } + return authorization; + } + } - foreach (var authorization in authorizations) - { - yield return authorization; - } - } + /// + public IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public async ValueTask RemoveAsync(TAuthorization authorization, CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - if (authorization is null) + var parameters = new { - throw new ArgumentNullException(nameof(authorization)); - } + Method = nameof(FindBySubjectAsync), + Subject = subject + }; - var identifier = await _store.GetIdAsync(authorization, cancellationToken); - if (string.IsNullOrEmpty(identifier)) + if (!_cache.TryGetValue(parameters, out ImmutableArray authorizations)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0196)); + var builder = ImmutableArray.CreateBuilder(); + + await foreach (var authorization in _store.FindBySubjectAsync(subject, cancellationToken)) + { + builder.Add(authorization); + + await AddAsync(authorization, cancellationToken); + } + + authorizations = builder.ToImmutable(); + + await CreateEntryAsync(parameters, authorizations, cancellationToken); } - if (_signals.TryRemove(identifier, out CancellationTokenSource? signal)) + foreach (var authorization in authorizations) { - signal.Cancel(); - signal.Dispose(); + yield return authorization; } } + } - /// - /// Creates a cache entry for the specified key. - /// - /// The cache key. - /// The authorization to store in the cache entry, if applicable. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - protected virtual async ValueTask CreateEntryAsync(object key, TAuthorization? authorization, CancellationToken cancellationToken) + /// + public async ValueTask RemoveAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } - - using var entry = _cache.CreateEntry(key); + throw new ArgumentNullException(nameof(authorization)); + } - if (authorization is not null) - { - var signal = await CreateExpirationSignalAsync(authorization, cancellationToken); - if (signal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); - } + var identifier = await _store.GetIdAsync(authorization, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0196)); + } - entry.AddExpirationToken(signal); - } + if (_signals.TryRemove(identifier, out CancellationTokenSource? signal)) + { + signal.Cancel(); + signal.Dispose(); + } + } - entry.SetSize(1L); - entry.SetValue(authorization); + /// + /// Creates a cache entry for the specified key. + /// + /// The cache key. + /// The authorization to store in the cache entry, if applicable. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + protected virtual async ValueTask CreateEntryAsync(object key, TAuthorization? authorization, CancellationToken cancellationToken) + { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); } - /// - /// Creates a cache entry for the specified key. - /// - /// The cache key. - /// The authorizations to store in the cache entry. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - protected virtual async ValueTask CreateEntryAsync( - object key, ImmutableArray authorizations, CancellationToken cancellationToken) + using var entry = _cache.CreateEntry(key); + + if (authorization is not null) { - if (key is null) + var signal = await CreateExpirationSignalAsync(authorization, cancellationToken); + if (signal is null) { - throw new ArgumentNullException(nameof(key)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); } - using var entry = _cache.CreateEntry(key); - - foreach (var authorization in authorizations) - { - var signal = await CreateExpirationSignalAsync(authorization, cancellationToken); - if (signal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); - } + entry.AddExpirationToken(signal); + } - entry.AddExpirationToken(signal); - } + entry.SetSize(1L); + entry.SetValue(authorization); + } - entry.SetSize(authorizations.Length); - entry.SetValue(authorizations); + /// + /// Creates a cache entry for the specified key. + /// + /// The cache key. + /// The authorizations to store in the cache entry. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + protected virtual async ValueTask CreateEntryAsync( + object key, ImmutableArray authorizations, CancellationToken cancellationToken) + { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); } - /// - /// Creates an expiration signal allowing to invalidate all the - /// cache entries associated with the specified authorization. - /// - /// The authorization associated with the expiration signal. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns an expiration signal for the specified authorization. - /// - protected virtual async ValueTask CreateExpirationSignalAsync( - TAuthorization authorization, CancellationToken cancellationToken) + using var entry = _cache.CreateEntry(key); + + foreach (var authorization in authorizations) { - if (authorization is null) + var signal = await CreateExpirationSignalAsync(authorization, cancellationToken); + if (signal is null) { - throw new ArgumentNullException(nameof(authorization)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); } - var identifier = await _store.GetIdAsync(authorization, cancellationToken); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0201)); - } + entry.AddExpirationToken(signal); + } - var signal = _signals.GetOrAdd(identifier, _ => new CancellationTokenSource()); + entry.SetSize(authorizations.Length); + entry.SetValue(authorizations); + } - return new CancellationChangeToken(signal.Token); + /// + /// Creates an expiration signal allowing to invalidate all the + /// cache entries associated with the specified authorization. + /// + /// The authorization associated with the expiration signal. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns an expiration signal for the specified authorization. + /// + protected virtual async ValueTask CreateExpirationSignalAsync( + TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } + + var identifier = await _store.GetIdAsync(authorization, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0201)); + } + + var signal = _signals.GetOrAdd(identifier, _ => new CancellationTokenSource()); + + return new CancellationChangeToken(signal.Token); } } diff --git a/src/OpenIddict.Core/Caches/OpenIddictScopeCache.cs b/src/OpenIddict.Core/Caches/OpenIddictScopeCache.cs index ff9c9a1b..0b5b0e0f 100644 --- a/src/OpenIddict.Core/Caches/OpenIddictScopeCache.cs +++ b/src/OpenIddict.Core/Caches/OpenIddictScopeCache.cs @@ -18,326 +18,325 @@ using Microsoft.Extensions.Primitives; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Provides methods allowing to cache scopes after retrieving them from the store. +/// +/// The type of the Scope entity. +public class OpenIddictScopeCache : IOpenIddictScopeCache, IDisposable where TScope : class { - /// - /// Provides methods allowing to cache scopes after retrieving them from the store. - /// - /// The type of the Scope entity. - public class OpenIddictScopeCache : IOpenIddictScopeCache, IDisposable where TScope : class - { - private readonly MemoryCache _cache; - private readonly ConcurrentDictionary _signals; - private readonly IOpenIddictScopeStore _store; + private readonly MemoryCache _cache; + private readonly ConcurrentDictionary _signals; + private readonly IOpenIddictScopeStore _store; - public OpenIddictScopeCache( - IOptionsMonitor options, - IOpenIddictScopeStoreResolver resolver) + public OpenIddictScopeCache( + IOptionsMonitor options, + IOpenIddictScopeStoreResolver resolver) + { + _cache = new MemoryCache(new MemoryCacheOptions { - _cache = new MemoryCache(new MemoryCacheOptions - { - SizeLimit = options.CurrentValue.EntityCacheLimit - }); + SizeLimit = options.CurrentValue.EntityCacheLimit + }); - _signals = new ConcurrentDictionary(StringComparer.Ordinal); - _store = resolver.Get(); + _signals = new ConcurrentDictionary(StringComparer.Ordinal); + _store = resolver.Get(); + } + + /// + public async ValueTask AddAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public async ValueTask AddAsync(TScope scope, CancellationToken cancellationToken) + _cache.Remove(new { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + Method = nameof(FindByIdAsync), + Identifier = await _store.GetIdAsync(scope, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindByIdAsync), - Identifier = await _store.GetIdAsync(scope, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindByNameAsync), + Name = await _store.GetNameAsync(scope, cancellationToken) + }); + foreach (var resource in await _store.GetResourcesAsync(scope, cancellationToken)) + { _cache.Remove(new { - Method = nameof(FindByNameAsync), - Name = await _store.GetNameAsync(scope, cancellationToken) + Method = nameof(FindByResourceAsync), + Resource = resource }); + } - foreach (var resource in await _store.GetResourcesAsync(scope, cancellationToken)) - { - _cache.Remove(new - { - Method = nameof(FindByResourceAsync), - Resource = resource - }); - } + await CreateEntryAsync(new + { + Method = nameof(FindByIdAsync), + Identifier = await _store.GetIdAsync(scope, cancellationToken) + }, scope, cancellationToken); - await CreateEntryAsync(new - { - Method = nameof(FindByIdAsync), - Identifier = await _store.GetIdAsync(scope, cancellationToken) - }, scope, cancellationToken); + await CreateEntryAsync(new + { + Method = nameof(FindByNameAsync), + Name = await _store.GetNameAsync(scope, cancellationToken) + }, scope, cancellationToken); + } - await CreateEntryAsync(new - { - Method = nameof(FindByNameAsync), - Name = await _store.GetNameAsync(scope, cancellationToken) - }, scope, cancellationToken); + /// + public void Dispose() + { + foreach (var signal in _signals) + { + signal.Value.Dispose(); } - /// - public void Dispose() - { - foreach (var signal in _signals) - { - signal.Value.Dispose(); - } + _cache.Dispose(); + } - _cache.Dispose(); + /// + public ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + var parameters = new { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + Method = nameof(FindByIdAsync), + Identifier = identifier + }; - var parameters = new - { - Method = nameof(FindByIdAsync), - Identifier = identifier - }; + if (_cache.TryGetValue(parameters, out TScope? scope)) + { + return new ValueTask(scope); + } - if (_cache.TryGetValue(parameters, out TScope? scope)) + return new ValueTask(ExecuteAsync()); + + async Task ExecuteAsync() + { + if ((scope = await _store.FindByIdAsync(identifier, cancellationToken)) is not null) { - return new ValueTask(scope); + await AddAsync(scope, cancellationToken); } - return new ValueTask(ExecuteAsync()); + await CreateEntryAsync(parameters, scope, cancellationToken); - async Task ExecuteAsync() - { - if ((scope = await _store.FindByIdAsync(identifier, cancellationToken)) is not null) - { - await AddAsync(scope, cancellationToken); - } - - await CreateEntryAsync(parameters, scope, cancellationToken); + return scope; + } + } - return scope; - } + /// + public ValueTask FindByNameAsync(string name, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); } - /// - public ValueTask FindByNameAsync(string name, CancellationToken cancellationToken) + var parameters = new { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); - } + Method = nameof(FindByNameAsync), + Name = name + }; - var parameters = new - { - Method = nameof(FindByNameAsync), - Name = name - }; + if (_cache.TryGetValue(parameters, out TScope? scope)) + { + return new ValueTask(scope); + } - if (_cache.TryGetValue(parameters, out TScope? scope)) + async Task ExecuteAsync() + { + if ((scope = await _store.FindByNameAsync(name, cancellationToken)) is not null) { - return new ValueTask(scope); + await AddAsync(scope, cancellationToken); } - async Task ExecuteAsync() - { - if ((scope = await _store.FindByNameAsync(name, cancellationToken)) is not null) - { - await AddAsync(scope, cancellationToken); - } - - await CreateEntryAsync(parameters, scope, cancellationToken); + await CreateEntryAsync(parameters, scope, cancellationToken); - return scope; - } - - return new ValueTask(ExecuteAsync()); + return scope; } - /// - public IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken) + return new ValueTask(ExecuteAsync()); + } + + /// + public IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken) + { + if (names.Any(name => string.IsNullOrEmpty(name))) { - if (names.Any(name => string.IsNullOrEmpty(name))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); + } - // Note: this method is only partially cached. + // Note: this method is only partially cached. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var scope in _store.FindByNamesAsync(names, cancellationToken)) { - await foreach (var scope in _store.FindByNamesAsync(names, cancellationToken)) - { - await AddAsync(scope, cancellationToken); + await AddAsync(scope, cancellationToken); - yield return scope; - } + yield return scope; } } + } - /// - public IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken) + /// + public IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(resource)) { - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); + } - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var parameters = new { - var parameters = new - { - Method = nameof(FindByResourceAsync), - Resource = resource - }; + Method = nameof(FindByResourceAsync), + Resource = resource + }; - if (!_cache.TryGetValue(parameters, out ImmutableArray scopes)) - { - var builder = ImmutableArray.CreateBuilder(); + if (!_cache.TryGetValue(parameters, out ImmutableArray scopes)) + { + var builder = ImmutableArray.CreateBuilder(); - await foreach (var scope in _store.FindByResourceAsync(resource, cancellationToken)) - { - builder.Add(scope); + await foreach (var scope in _store.FindByResourceAsync(resource, cancellationToken)) + { + builder.Add(scope); - await AddAsync(scope, cancellationToken); - } + await AddAsync(scope, cancellationToken); + } - scopes = builder.ToImmutable(); + scopes = builder.ToImmutable(); - await CreateEntryAsync(parameters, scopes, cancellationToken); - } + await CreateEntryAsync(parameters, scopes, cancellationToken); + } - foreach (var scope in scopes) - { - yield return scope; - } + foreach (var scope in scopes) + { + yield return scope; } } + } - /// - public async ValueTask RemoveAsync(TScope scope, CancellationToken cancellationToken) + /// + public async ValueTask RemoveAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - var identifier = await _store.GetIdAsync(scope, cancellationToken); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0196)); - } + var identifier = await _store.GetIdAsync(scope, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0196)); + } - if (_signals.TryRemove(identifier, out CancellationTokenSource? signal)) - { - signal.Cancel(); - signal.Dispose(); - } + if (_signals.TryRemove(identifier, out CancellationTokenSource? signal)) + { + signal.Cancel(); + signal.Dispose(); } + } - /// - /// Creates a cache entry for the specified key. - /// - /// The cache key. - /// The scope to store in the cache entry, if applicable. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - protected virtual async ValueTask CreateEntryAsync(object key, TScope? scope, CancellationToken cancellationToken) + /// + /// Creates a cache entry for the specified key. + /// + /// The cache key. + /// The scope to store in the cache entry, if applicable. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + protected virtual async ValueTask CreateEntryAsync(object key, TScope? scope, CancellationToken cancellationToken) + { + if (key is null) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } + throw new ArgumentNullException(nameof(key)); + } - using var entry = _cache.CreateEntry(key); + using var entry = _cache.CreateEntry(key); - if (scope is not null) + if (scope is not null) + { + var signal = await CreateExpirationSignalAsync(scope, cancellationToken); + if (signal is null) { - var signal = await CreateExpirationSignalAsync(scope, cancellationToken); - if (signal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); - } - - entry.AddExpirationToken(signal); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); } - entry.SetSize(1L); - entry.SetValue(scope); + entry.AddExpirationToken(signal); } - /// - /// Creates a cache entry for the specified key. - /// - /// The cache key. - /// The scopes to store in the cache entry. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - protected virtual async ValueTask CreateEntryAsync( - object key, ImmutableArray scopes, CancellationToken cancellationToken) + entry.SetSize(1L); + entry.SetValue(scope); + } + + /// + /// Creates a cache entry for the specified key. + /// + /// The cache key. + /// The scopes to store in the cache entry. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + protected virtual async ValueTask CreateEntryAsync( + object key, ImmutableArray scopes, CancellationToken cancellationToken) + { + if (key is null) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } + throw new ArgumentNullException(nameof(key)); + } - using var entry = _cache.CreateEntry(key); + using var entry = _cache.CreateEntry(key); - foreach (var scope in scopes) + foreach (var scope in scopes) + { + var signal = await CreateExpirationSignalAsync(scope, cancellationToken); + if (signal is null) { - var signal = await CreateExpirationSignalAsync(scope, cancellationToken); - if (signal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); - } - - entry.AddExpirationToken(signal); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); } - entry.SetSize(scopes.Length); - entry.SetValue(scopes); + entry.AddExpirationToken(signal); } - /// - /// Creates an expiration signal allowing to invalidate all the - /// cache entries associated with the specified scope. - /// - /// The scope associated with the expiration signal. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns an expiration signal for the specified scope. - /// - protected virtual async ValueTask CreateExpirationSignalAsync(TScope scope, CancellationToken cancellationToken) + entry.SetSize(scopes.Length); + entry.SetValue(scopes); + } + + /// + /// Creates an expiration signal allowing to invalidate all the + /// cache entries associated with the specified scope. + /// + /// The scope associated with the expiration signal. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns an expiration signal for the specified scope. + /// + protected virtual async ValueTask CreateExpirationSignalAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - var identifier = await _store.GetIdAsync(scope, cancellationToken); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0204)); - } + var identifier = await _store.GetIdAsync(scope, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0204)); + } - var signal = _signals.GetOrAdd(identifier, _ => new CancellationTokenSource()); + var signal = _signals.GetOrAdd(identifier, _ => new CancellationTokenSource()); - return new CancellationChangeToken(signal.Token); - } + return new CancellationChangeToken(signal.Token); } } diff --git a/src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs b/src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs index e33a6010..38854f4c 100644 --- a/src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs +++ b/src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs @@ -17,581 +17,580 @@ using Microsoft.Extensions.Primitives; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Provides methods allowing to cache tokens after retrieving them from the store. +/// +/// The type of the Token entity. +public class OpenIddictTokenCache : IOpenIddictTokenCache, IDisposable where TToken : class { - /// - /// Provides methods allowing to cache tokens after retrieving them from the store. - /// - /// The type of the Token entity. - public class OpenIddictTokenCache : IOpenIddictTokenCache, IDisposable where TToken : class - { - private readonly MemoryCache _cache; - private readonly ConcurrentDictionary _signals; - private readonly IOpenIddictTokenStore _store; + private readonly MemoryCache _cache; + private readonly ConcurrentDictionary _signals; + private readonly IOpenIddictTokenStore _store; - public OpenIddictTokenCache( - IOptionsMonitor options, - IOpenIddictTokenStoreResolver resolver) + public OpenIddictTokenCache( + IOptionsMonitor options, + IOpenIddictTokenStoreResolver resolver) + { + _cache = new MemoryCache(new MemoryCacheOptions { - _cache = new MemoryCache(new MemoryCacheOptions - { - SizeLimit = options.CurrentValue.EntityCacheLimit - }); + SizeLimit = options.CurrentValue.EntityCacheLimit + }); - _signals = new ConcurrentDictionary(StringComparer.Ordinal); - _store = resolver.Get(); - } + _signals = new ConcurrentDictionary(StringComparer.Ordinal); + _store = resolver.Get(); + } - /// - public async ValueTask AddAsync(TToken token, CancellationToken cancellationToken) + /// + public async ValueTask AddAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(token, cancellationToken), - Client = await _store.GetApplicationIdAsync(token, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindAsync), + Subject = await _store.GetSubjectAsync(token, cancellationToken), + Client = await _store.GetApplicationIdAsync(token, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(token, cancellationToken), - Client = await _store.GetApplicationIdAsync(token, cancellationToken), - Status = await _store.GetStatusAsync(token, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindAsync), + Subject = await _store.GetSubjectAsync(token, cancellationToken), + Client = await _store.GetApplicationIdAsync(token, cancellationToken), + Status = await _store.GetStatusAsync(token, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindAsync), - Subject = await _store.GetSubjectAsync(token, cancellationToken), - Client = await _store.GetApplicationIdAsync(token, cancellationToken), - Status = await _store.GetStatusAsync(token, cancellationToken), - Type = await _store.GetTypeAsync(token, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindAsync), + Subject = await _store.GetSubjectAsync(token, cancellationToken), + Client = await _store.GetApplicationIdAsync(token, cancellationToken), + Status = await _store.GetStatusAsync(token, cancellationToken), + Type = await _store.GetTypeAsync(token, cancellationToken) + }); + + _cache.Remove(new + { + Method = nameof(FindByApplicationIdAsync), + Identifier = await _store.GetApplicationIdAsync(token, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindByApplicationIdAsync), - Identifier = await _store.GetApplicationIdAsync(token, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindByAuthorizationIdAsync), + Identifier = await _store.GetAuthorizationIdAsync(token, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindByAuthorizationIdAsync), - Identifier = await _store.GetAuthorizationIdAsync(token, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindByIdAsync), + Identifier = await _store.GetIdAsync(token, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindByIdAsync), - Identifier = await _store.GetIdAsync(token, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindByReferenceIdAsync), + Identifier = await _store.GetReferenceIdAsync(token, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindByReferenceIdAsync), - Identifier = await _store.GetReferenceIdAsync(token, cancellationToken) - }); + _cache.Remove(new + { + Method = nameof(FindBySubjectAsync), + Subject = await _store.GetSubjectAsync(token, cancellationToken) + }); - _cache.Remove(new - { - Method = nameof(FindBySubjectAsync), - Subject = await _store.GetSubjectAsync(token, cancellationToken) - }); + await CreateEntryAsync(new + { + Method = nameof(FindByIdAsync), + Identifier = await _store.GetIdAsync(token, cancellationToken) + }, token, cancellationToken); - await CreateEntryAsync(new - { - Method = nameof(FindByIdAsync), - Identifier = await _store.GetIdAsync(token, cancellationToken) - }, token, cancellationToken); + await CreateEntryAsync(new + { + Method = nameof(FindByReferenceIdAsync), + Identifier = await _store.GetReferenceIdAsync(token, cancellationToken) + }, token, cancellationToken); + } - await CreateEntryAsync(new - { - Method = nameof(FindByReferenceIdAsync), - Identifier = await _store.GetReferenceIdAsync(token, cancellationToken) - }, token, cancellationToken); + /// + public void Dispose() + { + foreach (var signal in _signals) + { + signal.Value.Dispose(); } - /// - public void Dispose() + _cache.Dispose(); + } + + /// + public IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) { - foreach (var signal in _signals) - { - signal.Value.Dispose(); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - _cache.Dispose(); + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); } - /// - public IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) + var parameters = new { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + Method = nameof(FindAsync), + Subject = subject, + Client = client + }; - if (string.IsNullOrEmpty(client)) + if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - return ExecuteAsync(cancellationToken); + var builder = ImmutableArray.CreateBuilder(); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new + await foreach (var token in _store.FindAsync(subject, client, cancellationToken)) { - Method = nameof(FindAsync), - Subject = subject, - Client = client - }; + builder.Add(token); - if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) - { - var builder = ImmutableArray.CreateBuilder(); + await AddAsync(token, cancellationToken); + } - await foreach (var token in _store.FindAsync(subject, client, cancellationToken)) - { - builder.Add(token); + tokens = builder.ToImmutable(); - await AddAsync(token, cancellationToken); - } + await CreateEntryAsync(parameters, tokens, cancellationToken); + } - tokens = builder.ToImmutable(); + foreach (var token in tokens) + { + yield return token; + } + } + } - await CreateEntryAsync(parameters, tokens, cancellationToken); - } + /// + public IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - foreach (var token in tokens) - { - yield return token; - } - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); } - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(status)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + return ExecuteAsync(cancellationToken); - if (string.IsNullOrEmpty(status)) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var parameters = new { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - return ExecuteAsync(cancellationToken); + Method = nameof(FindAsync), + Subject = subject, + Client = client, + Status = status + }; - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client, - Status = status - }; + var builder = ImmutableArray.CreateBuilder(); - if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) + await foreach (var token in _store.FindAsync(subject, client, status, cancellationToken)) { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var token in _store.FindAsync(subject, client, status, cancellationToken)) - { - builder.Add(token); - - await AddAsync(token, cancellationToken); - } + builder.Add(token); - tokens = builder.ToImmutable(); - - await CreateEntryAsync(parameters, tokens, cancellationToken); + await AddAsync(token, cancellationToken); } - foreach (var token in tokens) - { - yield return token; - } - } - } + tokens = builder.ToImmutable(); - /// - public IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + await CreateEntryAsync(parameters, tokens, cancellationToken); } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) + foreach (var token in tokens) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + yield return token; } + } + } - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindAsync), - Subject = subject, - Client = client, - Status = status, - Type = type - }; - - if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var token in _store.FindAsync(subject, client, status, type, cancellationToken)) - { - builder.Add(token); - - await AddAsync(token, cancellationToken); - } + /// + public IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - tokens = builder.ToImmutable(); + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - await CreateEntryAsync(parameters, tokens, cancellationToken); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - foreach (var token in tokens) - { - yield return token; - } - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); } - /// - public IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(identifier)) + var parameters = new { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } - - return ExecuteAsync(cancellationToken); + Method = nameof(FindAsync), + Subject = subject, + Client = client, + Status = status, + Type = type + }; - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) { - var parameters = new - { - Method = nameof(FindByApplicationIdAsync), - Identifier = identifier - }; + var builder = ImmutableArray.CreateBuilder(); - if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) + await foreach (var token in _store.FindAsync(subject, client, status, type, cancellationToken)) { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var token in _store.FindByApplicationIdAsync(identifier, cancellationToken)) - { - builder.Add(token); + builder.Add(token); - await AddAsync(token, cancellationToken); - } + await AddAsync(token, cancellationToken); + } - tokens = builder.ToImmutable(); + tokens = builder.ToImmutable(); - await CreateEntryAsync(parameters, tokens, cancellationToken); - } + await CreateEntryAsync(parameters, tokens, cancellationToken); + } - foreach (var token in tokens) - { - yield return token; - } + foreach (var token in tokens) + { + yield return token; } } + } - /// - public IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) + /// + public IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var parameters = new { - var parameters = new - { - Method = nameof(FindByAuthorizationIdAsync), - Identifier = identifier - }; + Method = nameof(FindByApplicationIdAsync), + Identifier = identifier + }; - if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) - { - var builder = ImmutableArray.CreateBuilder(); + if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) + { + var builder = ImmutableArray.CreateBuilder(); - await foreach (var token in _store.FindByAuthorizationIdAsync(identifier, cancellationToken)) - { - builder.Add(token); + await foreach (var token in _store.FindByApplicationIdAsync(identifier, cancellationToken)) + { + builder.Add(token); - await AddAsync(token, cancellationToken); - } + await AddAsync(token, cancellationToken); + } - tokens = builder.ToImmutable(); + tokens = builder.ToImmutable(); - await CreateEntryAsync(parameters, tokens, cancellationToken); - } + await CreateEntryAsync(parameters, tokens, cancellationToken); + } - foreach (var token in tokens) - { - yield return token; - } + foreach (var token in tokens) + { + yield return token; } } + } - /// - public ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + /// + public IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var parameters = new { - Method = nameof(FindByIdAsync), + Method = nameof(FindByAuthorizationIdAsync), Identifier = identifier }; - if (_cache.TryGetValue(parameters, out TToken? token)) + if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) { - return new ValueTask(token); - } + var builder = ImmutableArray.CreateBuilder(); - return new ValueTask(ExecuteAsync()); - - async Task ExecuteAsync() - { - if ((token = await _store.FindByIdAsync(identifier, cancellationToken)) is not null) + await foreach (var token in _store.FindByAuthorizationIdAsync(identifier, cancellationToken)) { + builder.Add(token); + await AddAsync(token, cancellationToken); } - await CreateEntryAsync(parameters, token, cancellationToken); + tokens = builder.ToImmutable(); - return token; + await CreateEntryAsync(parameters, tokens, cancellationToken); } - } - /// - public ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) + foreach (var token in tokens) { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + yield return token; } + } + } - var parameters = new - { - Method = nameof(FindByReferenceIdAsync), - Identifier = identifier - }; + /// + public ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } + + var parameters = new + { + Method = nameof(FindByIdAsync), + Identifier = identifier + }; + + if (_cache.TryGetValue(parameters, out TToken? token)) + { + return new ValueTask(token); + } + + return new ValueTask(ExecuteAsync()); - if (_cache.TryGetValue(parameters, out TToken? token)) + async Task ExecuteAsync() + { + if ((token = await _store.FindByIdAsync(identifier, cancellationToken)) is not null) { - return new ValueTask(token); + await AddAsync(token, cancellationToken); } - return new ValueTask(ExecuteAsync()); + await CreateEntryAsync(parameters, token, cancellationToken); - async Task ExecuteAsync() - { - if ((token = await _store.FindByReferenceIdAsync(identifier, cancellationToken)) is not null) - { - await AddAsync(token, cancellationToken); - } + return token; + } + } - await CreateEntryAsync(parameters, token, cancellationToken); + /// + public ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - return token; - } + var parameters = new + { + Method = nameof(FindByReferenceIdAsync), + Identifier = identifier + }; + + if (_cache.TryGetValue(parameters, out TToken? token)) + { + return new ValueTask(token); } - /// - public IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) + return new ValueTask(ExecuteAsync()); + + async Task ExecuteAsync() { - if (string.IsNullOrEmpty(subject)) + if ((token = await _store.FindByReferenceIdAsync(identifier, cancellationToken)) is not null) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + await AddAsync(token, cancellationToken); } - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var parameters = new - { - Method = nameof(FindBySubjectAsync), - Identifier = subject - }; + await CreateEntryAsync(parameters, token, cancellationToken); - if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) - { - var builder = ImmutableArray.CreateBuilder(); + return token; + } + } - await foreach (var token in _store.FindBySubjectAsync(subject, cancellationToken)) - { - builder.Add(token); + /// + public IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - await AddAsync(token, cancellationToken); - } + return ExecuteAsync(cancellationToken); - tokens = builder.ToImmutable(); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var parameters = new + { + Method = nameof(FindBySubjectAsync), + Identifier = subject + }; - await CreateEntryAsync(parameters, tokens, cancellationToken); - } + if (!_cache.TryGetValue(parameters, out ImmutableArray tokens)) + { + var builder = ImmutableArray.CreateBuilder(); - foreach (var token in tokens) + await foreach (var token in _store.FindBySubjectAsync(subject, cancellationToken)) { - yield return token; + builder.Add(token); + + await AddAsync(token, cancellationToken); } - } - } - /// - public async ValueTask RemoveAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + tokens = builder.ToImmutable(); - var identifier = await _store.GetIdAsync(token, cancellationToken); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0205)); + await CreateEntryAsync(parameters, tokens, cancellationToken); } - if (_signals.TryRemove(identifier, out CancellationTokenSource? signal)) + foreach (var token in tokens) { - signal.Cancel(); - signal.Dispose(); + yield return token; } } + } - /// - /// Creates a cache entry for the specified key. - /// - /// The cache key. - /// The token to store in the cache entry, if applicable. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - protected virtual async ValueTask CreateEntryAsync(object key, TToken? token, CancellationToken cancellationToken) + /// + public async ValueTask RemoveAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } - - using var entry = _cache.CreateEntry(key); + throw new ArgumentNullException(nameof(token)); + } - if (token is not null) - { - var signal = await CreateExpirationSignalAsync(token, cancellationToken); - if (signal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); - } + var identifier = await _store.GetIdAsync(token, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0205)); + } - entry.AddExpirationToken(signal); - } + if (_signals.TryRemove(identifier, out CancellationTokenSource? signal)) + { + signal.Cancel(); + signal.Dispose(); + } + } - entry.SetSize(1L); - entry.SetValue(token); + /// + /// Creates a cache entry for the specified key. + /// + /// The cache key. + /// The token to store in the cache entry, if applicable. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + protected virtual async ValueTask CreateEntryAsync(object key, TToken? token, CancellationToken cancellationToken) + { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); } - /// - /// Creates a cache entry for the specified key. - /// - /// The cache key. - /// The tokens to store in the cache entry. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - protected virtual async ValueTask CreateEntryAsync( - object key, ImmutableArray tokens, CancellationToken cancellationToken) + using var entry = _cache.CreateEntry(key); + + if (token is not null) { - if (key is null) + var signal = await CreateExpirationSignalAsync(token, cancellationToken); + if (signal is null) { - throw new ArgumentNullException(nameof(key)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); } - using var entry = _cache.CreateEntry(key); - - foreach (var token in tokens) - { - var signal = await CreateExpirationSignalAsync(token, cancellationToken); - if (signal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); - } + entry.AddExpirationToken(signal); + } - entry.AddExpirationToken(signal); - } + entry.SetSize(1L); + entry.SetValue(token); + } - entry.SetSize(tokens.Length); - entry.SetValue(tokens); + /// + /// Creates a cache entry for the specified key. + /// + /// The cache key. + /// The tokens to store in the cache entry. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + protected virtual async ValueTask CreateEntryAsync( + object key, ImmutableArray tokens, CancellationToken cancellationToken) + { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); } - /// - /// Creates an expiration signal allowing to invalidate all the - /// cache entries associated with the specified token. - /// - /// The token associated with the expiration signal. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns an expiration signal for the specified token. - /// - protected virtual async ValueTask CreateExpirationSignalAsync(TToken token, CancellationToken cancellationToken) + using var entry = _cache.CreateEntry(key); + + foreach (var token in tokens) { - if (token is null) + var signal = await CreateExpirationSignalAsync(token, cancellationToken); + if (signal is null) { - throw new ArgumentNullException(nameof(token)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0197)); } - var identifier = await _store.GetIdAsync(token, cancellationToken); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0205)); - } + entry.AddExpirationToken(signal); + } - var signal = _signals.GetOrAdd(identifier, _ => new CancellationTokenSource()); + entry.SetSize(tokens.Length); + entry.SetValue(tokens); + } - return new CancellationChangeToken(signal.Token); + /// + /// Creates an expiration signal allowing to invalidate all the + /// cache entries associated with the specified token. + /// + /// The token associated with the expiration signal. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns an expiration signal for the specified token. + /// + protected virtual async ValueTask CreateExpirationSignalAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + var identifier = await _store.GetIdAsync(token, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0205)); } + + var signal = _signals.GetOrAdd(identifier, _ => new CancellationTokenSource()); + + return new CancellationChangeToken(signal.Token); } } diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index f167718f..945ff8fc 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -35,1650 +35,1649 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Utilities; #endif -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Provides methods allowing to manage the applications stored in the store. +/// +/// +/// Applications that do not want to depend on a specific entity type can use the non-generic +/// instead, for which the actual entity type +/// is resolved at runtime based on the default entity type registered in the core options. +/// +/// The type of the Application entity. +public class OpenIddictApplicationManager : IOpenIddictApplicationManager where TApplication : class { + public OpenIddictApplicationManager( + IOpenIddictApplicationCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictApplicationStoreResolver resolver) + { + Cache = cache; + Logger = logger; + Options = options; + Store = resolver.Get(); + } + /// - /// Provides methods allowing to manage the applications stored in the store. + /// Gets the cache associated with the current manager. /// - /// - /// Applications that do not want to depend on a specific entity type can use the non-generic - /// instead, for which the actual entity type - /// is resolved at runtime based on the default entity type registered in the core options. - /// - /// The type of the Application entity. - public class OpenIddictApplicationManager : IOpenIddictApplicationManager where TApplication : class - { - public OpenIddictApplicationManager( - IOpenIddictApplicationCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictApplicationStoreResolver resolver) - { - Cache = cache; - Logger = logger; - Options = options; - Store = resolver.Get(); - } - - /// - /// Gets the cache associated with the current manager. - /// - protected IOpenIddictApplicationCache Cache { get; } - - /// - /// Gets the logger associated with the current manager. - /// - protected ILogger Logger { get; } - - /// - /// Gets the options associated with the current manager. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the store associated with the current manager. - /// - protected IOpenIddictApplicationStore Store { get; } - - /// - /// Determines the number of applications that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of applications in the database. - /// - public virtual ValueTask CountAsync(CancellationToken cancellationToken = default) - => Store.CountAsync(cancellationToken); - - /// - /// Determines the number of applications that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of applications that match the specified query. - /// - public virtual ValueTask CountAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + protected IOpenIddictApplicationCache Cache { get; } - return Store.CountAsync(query, cancellationToken); - } - - /// - /// Creates a new application. - /// - /// The application to create. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken = default) - => CreateAsync(application, secret: null, cancellationToken); - - /// - /// Creates a new application. - /// Note: the default implementation automatically hashes the client - /// secret before storing it in the database, for security reasons. - /// - /// The application to create. - /// The client secret associated with the application, if applicable. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask CreateAsync(TApplication application, string? secret, CancellationToken cancellationToken = default) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + /// + /// Gets the logger associated with the current manager. + /// + protected ILogger Logger { get; } - if (!string.IsNullOrEmpty(await Store.GetClientSecretAsync(application, cancellationToken))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0206), nameof(application)); - } + /// + /// Gets the options associated with the current manager. + /// + protected IOptionsMonitor Options { get; } - // If no client type was specified, assume it's a public application if no secret was provided. - var type = await Store.GetClientTypeAsync(application, cancellationToken); - if (string.IsNullOrEmpty(type)) - { - await Store.SetClientTypeAsync(application, string.IsNullOrEmpty(secret) ? - ClientTypes.Public : ClientTypes.Confidential, cancellationToken); - } + /// + /// Gets the store associated with the current manager. + /// + protected IOpenIddictApplicationStore Store { get; } - // If a client secret was provided, obfuscate it. - if (!string.IsNullOrEmpty(secret)) - { - secret = await ObfuscateClientSecretAsync(secret, cancellationToken); - await Store.SetClientSecretAsync(application, secret, cancellationToken); - } + /// + /// Determines the number of applications that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of applications in the database. + /// + public virtual ValueTask CountAsync(CancellationToken cancellationToken = default) + => Store.CountAsync(cancellationToken); - var results = await GetValidationResultsAsync(application, cancellationToken); - if (results.Any(result => result != ValidationResult.Success)) - { - var builder = new StringBuilder(); - builder.AppendLine(SR.GetResourceString(SR.ID0207)); - builder.AppendLine(); + /// + /// Determines the number of applications that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of applications that match the specified query. + /// + public virtual ValueTask CountAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } - foreach (var result in results) - { - builder.AppendLine(result.ErrorMessage); - } + return Store.CountAsync(query, cancellationToken); + } - throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); - } + /// + /// Creates a new application. + /// + /// The application to create. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken = default) + => CreateAsync(application, secret: null, cancellationToken); + + /// + /// Creates a new application. + /// Note: the default implementation automatically hashes the client + /// secret before storing it in the database, for security reasons. + /// + /// The application to create. + /// The client secret associated with the application, if applicable. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask CreateAsync(TApplication application, string? secret, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } + + if (!string.IsNullOrEmpty(await Store.GetClientSecretAsync(application, cancellationToken))) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0206), nameof(application)); + } + + // If no client type was specified, assume it's a public application if no secret was provided. + var type = await Store.GetClientTypeAsync(application, cancellationToken); + if (string.IsNullOrEmpty(type)) + { + await Store.SetClientTypeAsync(application, string.IsNullOrEmpty(secret) ? + ClientTypes.Public : ClientTypes.Confidential, cancellationToken); + } - await Store.CreateAsync(application, cancellationToken); + // If a client secret was provided, obfuscate it. + if (!string.IsNullOrEmpty(secret)) + { + secret = await ObfuscateClientSecretAsync(secret, cancellationToken); + await Store.SetClientSecretAsync(application, secret, cancellationToken); + } - if (!Options.CurrentValue.DisableEntityCaching) + var results = await GetValidationResultsAsync(application, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) + { + var builder = new StringBuilder(); + builder.AppendLine(SR.GetResourceString(SR.ID0207)); + builder.AppendLine(); + + foreach (var result in results) { - await Cache.AddAsync(application, cancellationToken); + builder.AppendLine(result.ErrorMessage); } - async Task> GetValidationResultsAsync( - TApplication application, CancellationToken cancellationToken) - { - var builder = ImmutableArray.CreateBuilder(); + throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); + } - await foreach (var result in ValidateAsync(application, cancellationToken)) - { - builder.Add(result); - } + await Store.CreateAsync(application, cancellationToken); - return builder.ToImmutable(); - } + if (!Options.CurrentValue.DisableEntityCaching) + { + await Cache.AddAsync(application, cancellationToken); } - /// - /// Creates a new application based on the specified descriptor. - /// Note: the default implementation automatically hashes the client - /// secret before storing it in the database, for security reasons. - /// - /// The application descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the application. - /// - public virtual async ValueTask CreateAsync( - OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default) - { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + async Task> GetValidationResultsAsync( + TApplication application, CancellationToken cancellationToken) + { + var builder = ImmutableArray.CreateBuilder(); - var application = await Store.InstantiateAsync(cancellationToken); - if (application is null) + await foreach (var result in ValidateAsync(application, cancellationToken)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0208)); + builder.Add(result); } - await PopulateAsync(application, descriptor, cancellationToken); + return builder.ToImmutable(); + } + } - var secret = await Store.GetClientSecretAsync(application, cancellationToken); - if (!string.IsNullOrEmpty(secret)) - { - await Store.SetClientSecretAsync(application, secret: null, cancellationToken); - await CreateAsync(application, secret, cancellationToken); - } - else - { - await CreateAsync(application, cancellationToken); - } + /// + /// Creates a new application based on the specified descriptor. + /// Note: the default implementation automatically hashes the client + /// secret before storing it in the database, for security reasons. + /// + /// The application descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the application. + /// + public virtual async ValueTask CreateAsync( + OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (descriptor is null) + { + throw new ArgumentNullException(nameof(descriptor)); + } - return application; + var application = await Store.InstantiateAsync(cancellationToken); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0208)); } - /// - /// Removes an existing application. - /// - /// The application to delete. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken = default) + await PopulateAsync(application, descriptor, cancellationToken); + + var secret = await Store.GetClientSecretAsync(application, cancellationToken); + if (!string.IsNullOrEmpty(secret)) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + await Store.SetClientSecretAsync(application, secret: null, cancellationToken); + await CreateAsync(application, secret, cancellationToken); + } + else + { + await CreateAsync(application, cancellationToken); + } - if (!Options.CurrentValue.DisableEntityCaching) - { - await Cache.RemoveAsync(application, cancellationToken); - } + return application; + } - await Store.DeleteAsync(application, cancellationToken); + /// + /// Removes an existing application. + /// + /// The application to delete. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Retrieves an application using its client identifier. - /// - /// The client identifier associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client application corresponding to the identifier. - /// - public virtual async ValueTask FindByClientIdAsync( - string identifier, CancellationToken cancellationToken = default) + if (!Options.CurrentValue.DisableEntityCaching) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + await Cache.RemoveAsync(application, cancellationToken); + } - var application = Options.CurrentValue.DisableEntityCaching ? - await Store.FindByClientIdAsync(identifier, cancellationToken) : - await Cache.FindByClientIdAsync(identifier, cancellationToken); + await Store.DeleteAsync(application, cancellationToken); + } - if (application is null) - { - return null; - } + /// + /// Retrieves an application using its client identifier. + /// + /// The client identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + public virtual async ValueTask FindByClientIdAsync( + string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - if (!Options.CurrentValue.DisableAdditionalFiltering && - !string.Equals(await Store.GetClientIdAsync(application, cancellationToken), identifier, StringComparison.Ordinal)) - { - return null; - } + var application = Options.CurrentValue.DisableEntityCaching ? + await Store.FindByClientIdAsync(identifier, cancellationToken) : + await Cache.FindByClientIdAsync(identifier, cancellationToken); - return application; + if (application is null) + { + return null; } - /// - /// Retrieves an application using its unique identifier. - /// - /// The unique identifier associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client application corresponding to the identifier. - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default) + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + if (!Options.CurrentValue.DisableAdditionalFiltering && + !string.Equals(await Store.GetClientIdAsync(application, cancellationToken), identifier, StringComparison.Ordinal)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + return null; + } - var application = Options.CurrentValue.DisableEntityCaching ? - await Store.FindByIdAsync(identifier, cancellationToken) : - await Cache.FindByIdAsync(identifier, cancellationToken); + return application; + } - if (application is null) - { - return null; - } + /// + /// Retrieves an application using its unique identifier. + /// + /// The unique identifier associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client application corresponding to the identifier. + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - if (!Options.CurrentValue.DisableAdditionalFiltering && - !string.Equals(await Store.GetIdAsync(application, cancellationToken), identifier, StringComparison.Ordinal)) - { - return null; - } + var application = Options.CurrentValue.DisableEntityCaching ? + await Store.FindByIdAsync(identifier, cancellationToken) : + await Cache.FindByIdAsync(identifier, cancellationToken); - return application; + if (application is null) + { + return null; } - /// - /// Retrieves all the applications associated with the specified post_logout_redirect_uri. - /// - /// The post_logout_redirect_uri associated with the applications. - /// The that can be used to abort the operation. - /// The client applications corresponding to the specified post_logout_redirect_uri. - public virtual IAsyncEnumerable FindByPostLogoutRedirectUriAsync( - string address, CancellationToken cancellationToken = default) + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + if (!Options.CurrentValue.DisableAdditionalFiltering && + !string.Equals(await Store.GetIdAsync(application, cancellationToken), identifier, StringComparison.Ordinal)) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } + return null; + } - var applications = Options.CurrentValue.DisableEntityCaching ? - Store.FindByPostLogoutRedirectUriAsync(address, cancellationToken) : - Cache.FindByPostLogoutRedirectUriAsync(address, cancellationToken); + return application; + } - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return applications; - } + /// + /// Retrieves all the applications associated with the specified post_logout_redirect_uri. + /// + /// The post_logout_redirect_uri associated with the applications. + /// The that can be used to abort the operation. + /// The client applications corresponding to the specified post_logout_redirect_uri. + public virtual IAsyncEnumerable FindByPostLogoutRedirectUriAsync( + string address, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(address)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } - return ExecuteAsync(cancellationToken); + var applications = Options.CurrentValue.DisableEntityCaching ? + Store.FindByPostLogoutRedirectUriAsync(address, cancellationToken) : + Cache.FindByPostLogoutRedirectUriAsync(address, cancellationToken); - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return applications; + } + + return ExecuteAsync(cancellationToken); + + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var application in applications) { - await foreach (var application in applications) + var addresses = await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken); + if (addresses.Contains(address, StringComparer.Ordinal)) { - var addresses = await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken); - if (addresses.Contains(address, StringComparer.Ordinal)) - { - yield return application; - } + yield return application; } } } + } - /// - /// Retrieves all the applications associated with the specified redirect_uri. - /// - /// The redirect_uri associated with the applications. - /// The that can be used to abort the operation. - /// The client applications corresponding to the specified redirect_uri. - public virtual IAsyncEnumerable FindByRedirectUriAsync( - string address, CancellationToken cancellationToken = default) + /// + /// Retrieves all the applications associated with the specified redirect_uri. + /// + /// The redirect_uri associated with the applications. + /// The that can be used to abort the operation. + /// The client applications corresponding to the specified redirect_uri. + public virtual IAsyncEnumerable FindByRedirectUriAsync( + string address, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(address)) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } - var applications = Options.CurrentValue.DisableEntityCaching ? - Store.FindByRedirectUriAsync(address, cancellationToken) : - Cache.FindByRedirectUriAsync(address, cancellationToken); + var applications = Options.CurrentValue.DisableEntityCaching ? + Store.FindByRedirectUriAsync(address, cancellationToken) : + Cache.FindByRedirectUriAsync(address, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return applications; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return applications; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var application in applications) { - await foreach (var application in applications) + var addresses = await Store.GetRedirectUrisAsync(application, cancellationToken); + if (addresses.Contains(address, StringComparer.Ordinal)) { - var addresses = await Store.GetRedirectUrisAsync(application, cancellationToken); - if (addresses.Contains(address, StringComparer.Ordinal)) - { - yield return application; - } + yield return application; } } } + } - /// - /// Executes the specified query and returns the first element. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - public virtual ValueTask GetAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + /// + /// Executes the specified query and returns the first element. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + public virtual ValueTask GetAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } - return GetAsync(static (applications, query) => query(applications), query, cancellationToken); - } - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - public virtual ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + return GetAsync(static (applications, query) => query(applications), query, cancellationToken); + } - return Store.GetAsync(query, state, cancellationToken); + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + public virtual ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - /// Retrieves the client identifier associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client identifier associated with the application. - /// - public virtual ValueTask GetClientIdAsync( - TApplication application, CancellationToken cancellationToken = default) + return Store.GetAsync(query, state, cancellationToken); + } + + /// + /// Retrieves the client identifier associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client identifier associated with the application. + /// + public virtual ValueTask GetClientIdAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - return Store.GetClientIdAsync(application, cancellationToken); + return Store.GetClientIdAsync(application, cancellationToken); + } + + /// + /// Retrieves the client type associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the client type of the application (by default, "public"). + /// + public virtual ValueTask GetClientTypeAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Retrieves the client type associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the client type of the application (by default, "public"). - /// - public virtual ValueTask GetClientTypeAsync( - TApplication application, CancellationToken cancellationToken = default) + return Store.GetClientTypeAsync(application, cancellationToken); + } + + /// + /// Retrieves the consent type associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the consent type of the application (by default, "explicit"). + /// + public virtual async ValueTask GetConsentTypeAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - return Store.GetClientTypeAsync(application, cancellationToken); + var type = await Store.GetConsentTypeAsync(application, cancellationToken); + if (string.IsNullOrEmpty(type)) + { + return ConsentTypes.Explicit; } - /// - /// Retrieves the consent type associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the consent type of the application (by default, "explicit"). - /// - public virtual async ValueTask GetConsentTypeAsync( - TApplication application, CancellationToken cancellationToken = default) + return type; + } + + /// + /// Retrieves the display name associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the application. + /// + public virtual ValueTask GetDisplayNameAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - var type = await Store.GetConsentTypeAsync(application, cancellationToken); - if (string.IsNullOrEmpty(type)) - { - return ConsentTypes.Explicit; - } + return Store.GetDisplayNameAsync(application, cancellationToken); + } - return type; + /// + /// Retrieves the localized display names associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the localized display names associated with the application. + /// + public virtual async ValueTask> GetDisplayNamesAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Retrieves the display name associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the display name associated with the application. - /// - public virtual ValueTask GetDisplayNameAsync( - TApplication application, CancellationToken cancellationToken = default) + var names = await Store.GetDisplayNamesAsync(application, cancellationToken); + if (names is null || names.Count == 0) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return ImmutableDictionary.Create(); + } - return Store.GetDisplayNameAsync(application, cancellationToken); + return names; + } + + /// + /// Retrieves the unique identifier associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the application. + /// + public virtual ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Retrieves the localized display names associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the localized display names associated with the application. - /// - public virtual async ValueTask> GetDisplayNamesAsync( - TApplication application, CancellationToken cancellationToken = default) + return Store.GetIdAsync(application, cancellationToken); + } + + /// + /// Retrieves the localized display name associated with an application + /// and corresponding to the current UI culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching localized display name associated with the application. + /// + public virtual ValueTask GetLocalizedDisplayNameAsync( + TApplication application, CancellationToken cancellationToken = default) + => GetLocalizedDisplayNameAsync(application, CultureInfo.CurrentUICulture, cancellationToken); + + /// + /// Retrieves the localized display name associated with an application + /// and corresponding to the specified culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The application. + /// The culture (typically ). + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching localized display name associated with the application. + /// + public virtual async ValueTask GetLocalizedDisplayNameAsync( + TApplication application, CultureInfo culture, CancellationToken cancellationToken = default) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - var names = await Store.GetDisplayNamesAsync(application, cancellationToken); - if (names is null || names.Count == 0) - { - return ImmutableDictionary.Create(); - } + if (culture is null) + { + throw new ArgumentNullException(nameof(culture)); + } - return names; + var names = await Store.GetDisplayNamesAsync(application, cancellationToken); + if (names is null || names.IsEmpty) + { + return await Store.GetDisplayNameAsync(application, cancellationToken); } - /// - /// Retrieves the unique identifier associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the application. - /// - public virtual ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken = default) + do { - if (application is null) + if (names.TryGetValue(culture, out var name)) { - throw new ArgumentNullException(nameof(application)); + return name; } - return Store.GetIdAsync(application, cancellationToken); - } - - /// - /// Retrieves the localized display name associated with an application - /// and corresponding to the current UI culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching localized display name associated with the application. - /// - public virtual ValueTask GetLocalizedDisplayNameAsync( - TApplication application, CancellationToken cancellationToken = default) - => GetLocalizedDisplayNameAsync(application, CultureInfo.CurrentUICulture, cancellationToken); - - /// - /// Retrieves the localized display name associated with an application - /// and corresponding to the specified culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The application. - /// The culture (typically ). - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching localized display name associated with the application. - /// - public virtual async ValueTask GetLocalizedDisplayNameAsync( - TApplication application, CultureInfo culture, CancellationToken cancellationToken = default) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + culture = culture.Parent; + } - if (culture is null) - { - throw new ArgumentNullException(nameof(culture)); - } + while (culture != CultureInfo.InvariantCulture); - var names = await Store.GetDisplayNamesAsync(application, cancellationToken); - if (names is null || names.IsEmpty) - { - return await Store.GetDisplayNameAsync(application, cancellationToken); - } + return await Store.GetDisplayNameAsync(application, cancellationToken); + } - do - { - if (names.TryGetValue(culture, out var name)) - { - return name; - } + /// + /// Retrieves the permissions associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the permissions associated with the application. + /// + public virtual ValueTask> GetPermissionsAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - culture = culture.Parent; - } + return Store.GetPermissionsAsync(application, cancellationToken); + } - while (culture != CultureInfo.InvariantCulture); + /// + /// Retrieves the logout callback addresses associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the post_logout_redirect_uri associated with the application. + /// + public virtual ValueTask> GetPostLogoutRedirectUrisAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - return await Store.GetDisplayNameAsync(application, cancellationToken); + return Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken); + } + + /// + /// Retrieves the additional properties associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the application. + /// + public virtual ValueTask> GetPropertiesAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Retrieves the permissions associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the permissions associated with the application. - /// - public virtual ValueTask> GetPermissionsAsync( - TApplication application, CancellationToken cancellationToken = default) + return Store.GetPropertiesAsync(application, cancellationToken); + } + + /// + /// Retrieves the callback addresses associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the redirect_uri associated with the application. + /// + public virtual ValueTask> GetRedirectUrisAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - return Store.GetPermissionsAsync(application, cancellationToken); + return Store.GetRedirectUrisAsync(application, cancellationToken); + } + + /// + /// Retrieves the requirements associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the requirements associated with the application. + /// + public virtual ValueTask> GetRequirementsAsync( + TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } + + return Store.GetRequirementsAsync(application, cancellationToken); + } + + /// + /// Determines whether a given application has the specified client type. + /// + /// The application. + /// The expected client type. + /// The that can be used to abort the operation. + /// true if the application has the specified client type, false otherwise. + public virtual async ValueTask HasClientTypeAsync( + TApplication application, string type, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Retrieves the logout callback addresses associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the post_logout_redirect_uri associated with the application. - /// - public virtual ValueTask> GetPostLogoutRedirectUrisAsync( - TApplication application, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(type)) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0209), nameof(type)); + } - return Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken); + return string.Equals(await GetClientTypeAsync(application, cancellationToken), type, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Determines whether a given application has the specified consent type. + /// + /// The application. + /// The expected consent type. + /// The that can be used to abort the operation. + /// true if the application has the specified consent type, false otherwise. + public virtual async ValueTask HasConsentTypeAsync( + TApplication application, string type, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Retrieves the additional properties associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the application. - /// - public virtual ValueTask> GetPropertiesAsync( - TApplication application, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(type)) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0210), nameof(type)); + } + + return string.Equals(await GetConsentTypeAsync(application, cancellationToken), type, StringComparison.OrdinalIgnoreCase); + } - return Store.GetPropertiesAsync(application, cancellationToken); + /// + /// Determines whether the specified permission has been granted to the application. + /// + /// The application. + /// The permission. + /// The that can be used to abort the operation. + /// true if the application has been granted the specified permission, false otherwise. + public virtual async ValueTask HasPermissionAsync( + TApplication application, string permission, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } + + if (string.IsNullOrEmpty(permission)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0211), nameof(permission)); + } + + return (await GetPermissionsAsync(application, cancellationToken)).Contains(permission, StringComparer.Ordinal); + } + + /// + /// Determines whether the specified requirement has been enforced for the specified application. + /// + /// The application. + /// The requirement. + /// The that can be used to abort the operation. + /// true if the requirement has been enforced for the specified application, false otherwise. + public virtual async ValueTask HasRequirementAsync( + TApplication application, string requirement, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } + + if (string.IsNullOrEmpty(requirement)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0212), nameof(requirement)); + } + + return (await GetRequirementsAsync(application, cancellationToken)).Contains(requirement, StringComparer.Ordinal); + } + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + int? count = null, int? offset = null, CancellationToken cancellationToken = default) + => Store.ListAsync(count, offset, cancellationToken); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } + + return ListAsync(static (applications, query) => query(applications), query, cancellationToken); + } + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } + + return Store.ListAsync(query, state, cancellationToken); + } + + /// + /// Populates the application using the specified descriptor. + /// + /// The application. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask PopulateAsync(TApplication application, + OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Retrieves the callback addresses associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the redirect_uri associated with the application. - /// - public virtual ValueTask> GetRedirectUrisAsync( - TApplication application, CancellationToken cancellationToken = default) + if (descriptor is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - return Store.GetRedirectUrisAsync(application, cancellationToken); + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Retrieves the requirements associated with an application. - /// - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the requirements associated with the application. - /// - public virtual ValueTask> GetRequirementsAsync( - TApplication application, CancellationToken cancellationToken = default) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + await Store.SetClientIdAsync(application, descriptor.ClientId, cancellationToken); + await Store.SetClientSecretAsync(application, descriptor.ClientSecret, cancellationToken); + await Store.SetClientTypeAsync(application, descriptor.Type, cancellationToken); + await Store.SetConsentTypeAsync(application, descriptor.ConsentType, cancellationToken); + await Store.SetDisplayNameAsync(application, descriptor.DisplayName, cancellationToken); + await Store.SetDisplayNamesAsync(application, descriptor.DisplayNames.ToImmutableDictionary(), cancellationToken); + await Store.SetPermissionsAsync(application, descriptor.Permissions.ToImmutableArray(), cancellationToken); + await Store.SetPostLogoutRedirectUrisAsync(application, ImmutableArray.CreateRange( + descriptor.PostLogoutRedirectUris.Select(address => address.OriginalString)), cancellationToken); + await Store.SetPropertiesAsync(application, descriptor.Properties.ToImmutableDictionary(), cancellationToken); + await Store.SetRedirectUrisAsync(application, ImmutableArray.CreateRange( + descriptor.RedirectUris.Select(address => address.OriginalString)), cancellationToken); + await Store.SetRequirementsAsync(application, descriptor.Requirements.ToImmutableArray(), cancellationToken); + } - return Store.GetRequirementsAsync(application, cancellationToken); + /// + /// Populates the specified descriptor using the properties exposed by the application. + /// + /// The descriptor. + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask PopulateAsync( + OpenIddictApplicationDescriptor descriptor, + TApplication application, CancellationToken cancellationToken = default) + { + if (descriptor is null) + { + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Determines whether a given application has the specified client type. - /// - /// The application. - /// The expected client type. - /// The that can be used to abort the operation. - /// true if the application has the specified client type, false otherwise. - public virtual async ValueTask HasClientTypeAsync( - TApplication application, string type, CancellationToken cancellationToken = default) + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0209), nameof(type)); - } - - return string.Equals(await GetClientTypeAsync(application, cancellationToken), type, StringComparison.OrdinalIgnoreCase); + throw new ArgumentNullException(nameof(application)); } - /// - /// Determines whether a given application has the specified consent type. - /// - /// The application. - /// The expected consent type. - /// The that can be used to abort the operation. - /// true if the application has the specified consent type, false otherwise. - public virtual async ValueTask HasConsentTypeAsync( - TApplication application, string type, CancellationToken cancellationToken = default) + descriptor.ClientId = await Store.GetClientIdAsync(application, cancellationToken); + descriptor.ClientSecret = await Store.GetClientSecretAsync(application, cancellationToken); + descriptor.ConsentType = await Store.GetConsentTypeAsync(application, cancellationToken); + descriptor.DisplayName = await Store.GetDisplayNameAsync(application, cancellationToken); + descriptor.Type = await Store.GetClientTypeAsync(application, cancellationToken); + descriptor.Permissions.Clear(); + descriptor.Permissions.UnionWith(await Store.GetPermissionsAsync(application, cancellationToken)); + descriptor.Requirements.Clear(); + descriptor.Requirements.UnionWith(await Store.GetRequirementsAsync(application, cancellationToken)); + + descriptor.DisplayNames.Clear(); + foreach (var pair in await Store.GetDisplayNamesAsync(application, cancellationToken)) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0210), nameof(type)); - } - - return string.Equals(await GetConsentTypeAsync(application, cancellationToken), type, StringComparison.OrdinalIgnoreCase); + descriptor.DisplayNames.Add(pair.Key, pair.Value); } - /// - /// Determines whether the specified permission has been granted to the application. - /// - /// The application. - /// The permission. - /// The that can be used to abort the operation. - /// true if the application has been granted the specified permission, false otherwise. - public virtual async ValueTask HasPermissionAsync( - TApplication application, string permission, CancellationToken cancellationToken = default) + descriptor.PostLogoutRedirectUris.Clear(); + foreach (var address in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) { - if (application is null) + // Ensure the address is not null or empty. + if (string.IsNullOrEmpty(address)) { - throw new ArgumentNullException(nameof(application)); + throw new ArgumentException(SR.GetResourceString(SR.ID0213)); } - if (string.IsNullOrEmpty(permission)) + // Ensure the address is a valid absolute URL. + if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) { - throw new ArgumentException(SR.GetResourceString(SR.ID0211), nameof(permission)); + throw new ArgumentException(SR.GetResourceString(SR.ID0214)); } - return (await GetPermissionsAsync(application, cancellationToken)).Contains(permission, StringComparer.Ordinal); + descriptor.PostLogoutRedirectUris.Add(uri); } - /// - /// Determines whether the specified requirement has been enforced for the specified application. - /// - /// The application. - /// The requirement. - /// The that can be used to abort the operation. - /// true if the requirement has been enforced for the specified application, false otherwise. - public virtual async ValueTask HasRequirementAsync( - TApplication application, string requirement, CancellationToken cancellationToken = default) + descriptor.Properties.Clear(); + foreach (var pair in await Store.GetPropertiesAsync(application, cancellationToken)) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + descriptor.Properties.Add(pair.Key, pair.Value); + } - if (string.IsNullOrEmpty(requirement)) + descriptor.RedirectUris.Clear(); + foreach (var address in await Store.GetRedirectUrisAsync(application, cancellationToken)) + { + // Ensure the address is not null or empty. + if (string.IsNullOrEmpty(address)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0212), nameof(requirement)); + throw new ArgumentException(SR.GetResourceString(SR.ID0213)); } - return (await GetRequirementsAsync(application, cancellationToken)).Contains(requirement, StringComparer.Ordinal); - } - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - int? count = null, int? offset = null, CancellationToken cancellationToken = default) - => Store.ListAsync(count, offset, cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) - { - if (query is null) + // Ensure the address is a valid absolute URL. + if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) { - throw new ArgumentNullException(nameof(query)); + throw new ArgumentException(SR.GetResourceString(SR.ID0214)); } - return ListAsync(static (applications, query) => query(applications), query, cancellationToken); + descriptor.RedirectUris.Add(uri); } + } - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default) + /// + /// Updates an existing application. + /// + /// The application to update. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken = default) + { + if (application is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return Store.ListAsync(query, state, cancellationToken); + throw new ArgumentNullException(nameof(application)); } - /// - /// Populates the application using the specified descriptor. - /// - /// The application. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask PopulateAsync(TApplication application, - OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default) + var results = await GetValidationResultsAsync(application, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - await Store.SetClientIdAsync(application, descriptor.ClientId, cancellationToken); - await Store.SetClientSecretAsync(application, descriptor.ClientSecret, cancellationToken); - await Store.SetClientTypeAsync(application, descriptor.Type, cancellationToken); - await Store.SetConsentTypeAsync(application, descriptor.ConsentType, cancellationToken); - await Store.SetDisplayNameAsync(application, descriptor.DisplayName, cancellationToken); - await Store.SetDisplayNamesAsync(application, descriptor.DisplayNames.ToImmutableDictionary(), cancellationToken); - await Store.SetPermissionsAsync(application, descriptor.Permissions.ToImmutableArray(), cancellationToken); - await Store.SetPostLogoutRedirectUrisAsync(application, ImmutableArray.CreateRange( - descriptor.PostLogoutRedirectUris.Select(address => address.OriginalString)), cancellationToken); - await Store.SetPropertiesAsync(application, descriptor.Properties.ToImmutableDictionary(), cancellationToken); - await Store.SetRedirectUrisAsync(application, ImmutableArray.CreateRange( - descriptor.RedirectUris.Select(address => address.OriginalString)), cancellationToken); - await Store.SetRequirementsAsync(application, descriptor.Requirements.ToImmutableArray(), cancellationToken); - } - - /// - /// Populates the specified descriptor using the properties exposed by the application. - /// - /// The descriptor. - /// The application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask PopulateAsync( - OpenIddictApplicationDescriptor descriptor, - TApplication application, CancellationToken cancellationToken = default) - { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + var builder = new StringBuilder(); + builder.AppendLine(SR.GetResourceString(SR.ID0215)); + builder.AppendLine(); - if (application is null) + foreach (var result in results) { - throw new ArgumentNullException(nameof(application)); + builder.AppendLine(result.ErrorMessage); } - descriptor.ClientId = await Store.GetClientIdAsync(application, cancellationToken); - descriptor.ClientSecret = await Store.GetClientSecretAsync(application, cancellationToken); - descriptor.ConsentType = await Store.GetConsentTypeAsync(application, cancellationToken); - descriptor.DisplayName = await Store.GetDisplayNameAsync(application, cancellationToken); - descriptor.Type = await Store.GetClientTypeAsync(application, cancellationToken); - descriptor.Permissions.Clear(); - descriptor.Permissions.UnionWith(await Store.GetPermissionsAsync(application, cancellationToken)); - descriptor.Requirements.Clear(); - descriptor.Requirements.UnionWith(await Store.GetRequirementsAsync(application, cancellationToken)); - - descriptor.DisplayNames.Clear(); - foreach (var pair in await Store.GetDisplayNamesAsync(application, cancellationToken)) - { - descriptor.DisplayNames.Add(pair.Key, pair.Value); - } + throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); + } - descriptor.PostLogoutRedirectUris.Clear(); - foreach (var address in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) - { - // Ensure the address is not null or empty. - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0213)); - } + await Store.UpdateAsync(application, cancellationToken); - // Ensure the address is a valid absolute URL. - if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0214)); - } + if (!Options.CurrentValue.DisableEntityCaching) + { + await Cache.RemoveAsync(application, cancellationToken); + await Cache.AddAsync(application, cancellationToken); + } - descriptor.PostLogoutRedirectUris.Add(uri); - } + async Task> GetValidationResultsAsync( + TApplication application, CancellationToken cancellationToken) + { + var builder = ImmutableArray.CreateBuilder(); - descriptor.Properties.Clear(); - foreach (var pair in await Store.GetPropertiesAsync(application, cancellationToken)) + await foreach (var result in ValidateAsync(application, cancellationToken)) { - descriptor.Properties.Add(pair.Key, pair.Value); + builder.Add(result); } - descriptor.RedirectUris.Clear(); - foreach (var address in await Store.GetRedirectUrisAsync(application, cancellationToken)) - { - // Ensure the address is not null or empty. - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0213)); - } + return builder.ToImmutable(); + } + } - // Ensure the address is a valid absolute URL. - if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0214)); - } + /// + /// Updates an existing application and replaces the existing secret. + /// Note: the default implementation automatically hashes the client + /// secret before storing it in the database, for security reasons. + /// + /// The application to update. + /// The client secret associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask UpdateAsync(TApplication application, string? secret, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - descriptor.RedirectUris.Add(uri); - } + if (string.IsNullOrEmpty(secret)) + { + await Store.SetClientSecretAsync(application, null, cancellationToken); } - /// - /// Updates an existing application. - /// - /// The application to update. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken = default) + else { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + secret = await ObfuscateClientSecretAsync(secret, cancellationToken); + await Store.SetClientSecretAsync(application, secret, cancellationToken); + } - var results = await GetValidationResultsAsync(application, cancellationToken); - if (results.Any(result => result != ValidationResult.Success)) - { - var builder = new StringBuilder(); - builder.AppendLine(SR.GetResourceString(SR.ID0215)); - builder.AppendLine(); + await UpdateAsync(application, cancellationToken); + } - foreach (var result in results) - { - builder.AppendLine(result.ErrorMessage); - } + /// + /// Updates an existing application. + /// + /// The application to update. + /// The descriptor used to update the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask UpdateAsync(TApplication application, + OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); - } + if (descriptor is null) + { + throw new ArgumentNullException(nameof(descriptor)); + } - await Store.UpdateAsync(application, cancellationToken); + // Store the original client secret for later comparison. + var comparand = await Store.GetClientSecretAsync(application, cancellationToken); + await PopulateAsync(application, descriptor, cancellationToken); - if (!Options.CurrentValue.DisableEntityCaching) - { - await Cache.RemoveAsync(application, cancellationToken); - await Cache.AddAsync(application, cancellationToken); - } + // If the client secret was updated, use the overload accepting a secret parameter. + var secret = await Store.GetClientSecretAsync(application, cancellationToken); + if (!string.Equals(secret, comparand, StringComparison.Ordinal)) + { + await UpdateAsync(application, secret, cancellationToken); - async Task> GetValidationResultsAsync( - TApplication application, CancellationToken cancellationToken) - { - var builder = ImmutableArray.CreateBuilder(); + return; + } - await foreach (var result in ValidateAsync(application, cancellationToken)) - { - builder.Add(result); - } + await UpdateAsync(application, cancellationToken); + } - return builder.ToImmutable(); - } + /// + /// Validates the application to ensure it's in a consistent state. + /// + /// The application. + /// The that can be used to abort the operation. + /// The validation error encountered when validating the application. + public virtual async IAsyncEnumerable ValidateAsync( + TApplication application, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Updates an existing application and replaces the existing secret. - /// Note: the default implementation automatically hashes the client - /// secret before storing it in the database, for security reasons. - /// - /// The application to update. - /// The client secret associated with the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask UpdateAsync(TApplication application, string? secret, CancellationToken cancellationToken = default) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (string.IsNullOrEmpty(secret)) - { - await Store.SetClientSecretAsync(application, null, cancellationToken); - } + // Ensure the client_id is not null or empty and is not already used for a different application. + var identifier = await Store.GetClientIdAsync(application, cancellationToken); + if (string.IsNullOrEmpty(identifier)) + { + yield return new ValidationResult(SR.GetResourceString(SR.ID2036)); + } - else + else + { + // Note: depending on the database/table/query collation used by the store, an application + // whose client_id doesn't exactly match the specified value may be returned (e.g because + // the casing is different). To avoid issues when the client identifier is part of an index + // using the same collation, an error is added even if the two identifiers don't exactly match. + var other = await Store.FindByClientIdAsync(identifier, cancellationToken); + if (other is not null && !string.Equals( + await Store.GetIdAsync(other, cancellationToken), + await Store.GetIdAsync(application, cancellationToken), StringComparison.Ordinal)) { - secret = await ObfuscateClientSecretAsync(secret, cancellationToken); - await Store.SetClientSecretAsync(application, secret, cancellationToken); + yield return new ValidationResult(SR.GetResourceString(SR.ID2111)); } + } - await UpdateAsync(application, cancellationToken); + var type = await Store.GetClientTypeAsync(application, cancellationToken); + if (string.IsNullOrEmpty(type)) + { + yield return new ValidationResult(SR.GetResourceString(SR.ID2050)); } - /// - /// Updates an existing application. - /// - /// The application to update. - /// The descriptor used to update the application. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask UpdateAsync(TApplication application, - OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken = default) + else { - if (application is null) + // Ensure the application type is supported by the manager. + if (!string.Equals(type, ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) && + !string.Equals(type, ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentNullException(nameof(application)); + yield return new ValidationResult(SR.GetResourceString(SR.ID2112)); } - if (descriptor is null) + // Ensure a client secret was specified if the client is a confidential application. + var secret = await Store.GetClientSecretAsync(application, cancellationToken); + if (string.IsNullOrEmpty(secret) && string.Equals(type, ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentNullException(nameof(descriptor)); + yield return new ValidationResult(SR.GetResourceString(SR.ID2113)); } - // Store the original client secret for later comparison. - var comparand = await Store.GetClientSecretAsync(application, cancellationToken); - await PopulateAsync(application, descriptor, cancellationToken); - - // If the client secret was updated, use the overload accepting a secret parameter. - var secret = await Store.GetClientSecretAsync(application, cancellationToken); - if (!string.Equals(secret, comparand, StringComparison.Ordinal)) + // Ensure no client secret was specified if the client is a public application. + else if (!string.IsNullOrEmpty(secret) && string.Equals(type, ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) { - await UpdateAsync(application, secret, cancellationToken); - - return; + yield return new ValidationResult(SR.GetResourceString(SR.ID2114)); } - - await UpdateAsync(application, cancellationToken); } - /// - /// Validates the application to ensure it's in a consistent state. - /// - /// The application. - /// The that can be used to abort the operation. - /// The validation error encountered when validating the application. - public virtual async IAsyncEnumerable ValidateAsync( - TApplication application, [EnumeratorCancellation] CancellationToken cancellationToken = default) + // When callback URLs are specified, ensure they are valid and spec-compliant. + // See https://tools.ietf.org/html/rfc6749#section-3.1 for more information. + foreach (var address in ImmutableArray.Create() + .AddRange(await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) + .AddRange(await Store.GetRedirectUrisAsync(application, cancellationToken))) { - if (application is null) + // Ensure the address is not null or empty. + if (string.IsNullOrEmpty(address)) { - throw new ArgumentNullException(nameof(application)); - } + yield return new ValidationResult(SR.GetResourceString(SR.ID2061)); - // Ensure the client_id is not null or empty and is not already used for a different application. - var identifier = await Store.GetClientIdAsync(application, cancellationToken); - if (string.IsNullOrEmpty(identifier)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2036)); + break; } - else + // Ensure the address is a valid absolute URL. + if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) { - // Note: depending on the database/table/query collation used by the store, an application - // whose client_id doesn't exactly match the specified value may be returned (e.g because - // the casing is different). To avoid issues when the client identifier is part of an index - // using the same collation, an error is added even if the two identifiers don't exactly match. - var other = await Store.FindByClientIdAsync(identifier, cancellationToken); - if (other is not null && !string.Equals( - await Store.GetIdAsync(other, cancellationToken), - await Store.GetIdAsync(application, cancellationToken), StringComparison.Ordinal)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2111)); - } - } + yield return new ValidationResult(SR.GetResourceString(SR.ID2062)); - var type = await Store.GetClientTypeAsync(application, cancellationToken); - if (string.IsNullOrEmpty(type)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2050)); + break; } - else + // Ensure the address doesn't contain a fragment. + if (!string.IsNullOrEmpty(uri.Fragment)) { - // Ensure the application type is supported by the manager. - if (!string.Equals(type, ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) && - !string.Equals(type, ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2112)); - } - - // Ensure a client secret was specified if the client is a confidential application. - var secret = await Store.GetClientSecretAsync(application, cancellationToken); - if (string.IsNullOrEmpty(secret) && string.Equals(type, ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2113)); - } + yield return new ValidationResult(SR.GetResourceString(SR.ID2115)); - // Ensure no client secret was specified if the client is a public application. - else if (!string.IsNullOrEmpty(secret) && string.Equals(type, ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2114)); - } + break; } + } + } - // When callback URLs are specified, ensure they are valid and spec-compliant. - // See https://tools.ietf.org/html/rfc6749#section-3.1 for more information. - foreach (var address in ImmutableArray.Create() - .AddRange(await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken)) - .AddRange(await Store.GetRedirectUrisAsync(application, cancellationToken))) - { - // Ensure the address is not null or empty. - if (string.IsNullOrEmpty(address)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2061)); - - break; - } - - // Ensure the address is a valid absolute URL. - if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2062)); - - break; - } - - // Ensure the address doesn't contain a fragment. - if (!string.IsNullOrEmpty(uri.Fragment)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2115)); - - break; - } - } + /// + /// Validates the client_secret associated with an application. + /// + /// The application. + /// The secret that should be compared to the client_secret stored in the database. + /// The that can be used to abort the operation. + /// A that can be used to monitor the asynchronous operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns a boolean indicating whether the client secret was valid. + /// + public virtual async ValueTask ValidateClientSecretAsync( + TApplication application, string secret, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } + if (string.IsNullOrEmpty(secret)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0216), nameof(secret)); } - /// - /// Validates the client_secret associated with an application. - /// - /// The application. - /// The secret that should be compared to the client_secret stored in the database. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns a boolean indicating whether the client secret was valid. - /// - public virtual async ValueTask ValidateClientSecretAsync( - TApplication application, string secret, CancellationToken cancellationToken = default) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - if (string.IsNullOrEmpty(secret)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0216), nameof(secret)); - } + if (await HasClientTypeAsync(application, ClientTypes.Public, cancellationToken)) + { + Logger.LogWarning(SR.GetResourceString(SR.ID6159)); - if (await HasClientTypeAsync(application, ClientTypes.Public, cancellationToken)) - { - Logger.LogWarning(SR.GetResourceString(SR.ID6159)); + return false; + } - return false; - } + var value = await Store.GetClientSecretAsync(application, cancellationToken); + if (string.IsNullOrEmpty(value)) + { + Logger.LogError(SR.GetResourceString(SR.ID6160), await GetClientIdAsync(application, cancellationToken)); - var value = await Store.GetClientSecretAsync(application, cancellationToken); - if (string.IsNullOrEmpty(value)) - { - Logger.LogError(SR.GetResourceString(SR.ID6160), await GetClientIdAsync(application, cancellationToken)); + return false; + } - return false; - } + if (!await ValidateClientSecretAsync(secret, value, cancellationToken)) + { + Logger.LogInformation(SR.GetResourceString(SR.ID6161), await GetClientIdAsync(application, cancellationToken)); - if (!await ValidateClientSecretAsync(secret, value, cancellationToken)) - { - Logger.LogInformation(SR.GetResourceString(SR.ID6161), await GetClientIdAsync(application, cancellationToken)); + return false; + } - return false; - } + return true; + } - return true; + /// + /// Validates the redirect_uri to ensure it's associated with an application. + /// + /// The application. + /// The address that should be compared to one of the redirect_uri stored in the database. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns a boolean indicating whether the redirect_uri was valid. + /// + public virtual async ValueTask ValidateRedirectUriAsync( + TApplication application, string address, CancellationToken cancellationToken = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Validates the redirect_uri to ensure it's associated with an application. - /// - /// The application. - /// The address that should be compared to one of the redirect_uri stored in the database. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns a boolean indicating whether the redirect_uri was valid. - /// - public virtual async ValueTask ValidateRedirectUriAsync( - TApplication application, string address, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(address)) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } - foreach (var uri in await Store.GetRedirectUrisAsync(application, cancellationToken)) + foreach (var uri in await Store.GetRedirectUrisAsync(application, cancellationToken)) + { + // Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison". + // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information. + if (string.Equals(uri, address, StringComparison.Ordinal)) { - // Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison". - // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information. - if (string.Equals(uri, address, StringComparison.Ordinal)) - { - return true; - } + return true; } + } - Logger.LogInformation(SR.GetResourceString(SR.ID6162), address, await GetClientIdAsync(application, cancellationToken)); + Logger.LogInformation(SR.GetResourceString(SR.ID6162), address, await GetClientIdAsync(application, cancellationToken)); - return false; - } + return false; + } - /// - /// Obfuscates the specified client secret so it can be safely stored in a database. - /// By default, this method returns a complex hashed representation computed using PBKDF2. - /// - /// The client secret. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual ValueTask ObfuscateClientSecretAsync(string secret, CancellationToken cancellationToken = default) + /// + /// Obfuscates the specified client secret so it can be safely stored in a database. + /// By default, this method returns a complex hashed representation computed using PBKDF2. + /// + /// The client secret. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + protected virtual ValueTask ObfuscateClientSecretAsync(string secret, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(secret)) { - if (string.IsNullOrEmpty(secret)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0216), nameof(secret)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0216), nameof(secret)); + } - // Note: the PRF, iteration count, salt length and key length currently all match the default values - // used by CryptoHelper and ASP.NET Core Identity but this may change in the future, if necessary. + // Note: the PRF, iteration count, salt length and key length currently all match the default values + // used by CryptoHelper and ASP.NET Core Identity but this may change in the future, if necessary. - var salt = new byte[128 / 8]; + var salt = new byte[128 / 8]; #if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(salt); + RandomNumberGenerator.Fill(salt); #else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(salt); + using var generator = RandomNumberGenerator.Create(); + generator.GetBytes(salt); #endif - var hash = HashSecret(secret, salt, HashAlgorithmName.SHA256, iterations: 10_000, length: 256 / 8); + var hash = HashSecret(secret, salt, HashAlgorithmName.SHA256, iterations: 10_000, length: 256 / 8); - return new ValueTask( + return new ValueTask( #if SUPPORTS_BASE64_SPAN_CONVERSION - Convert.ToBase64String(hash) + Convert.ToBase64String(hash) #else - Convert.ToBase64String(hash.ToArray()) + Convert.ToBase64String(hash.ToArray()) #endif - ); + ); + + // Note: the following logic deliberately uses the same format as CryptoHelper (used in OpenIddict 1.x/2.x), + // which was itself based on ASP.NET Core Identity's latest hashed password format. This guarantees that + // secrets hashed using a recent OpenIddict version can still be read by older packages (and vice versa). - // Note: the following logic deliberately uses the same format as CryptoHelper (used in OpenIddict 1.x/2.x), - // which was itself based on ASP.NET Core Identity's latest hashed password format. This guarantees that - // secrets hashed using a recent OpenIddict version can still be read by older packages (and vice versa). + static ReadOnlySpan HashSecret(string secret, ReadOnlySpan salt, + HashAlgorithmName algorithm, int iterations, int length) + { + var key = DeriveKey(secret, salt, algorithm, iterations, length); + var payload = new Span(new byte[13 + salt.Length + key.Length]); - static ReadOnlySpan HashSecret(string secret, ReadOnlySpan salt, - HashAlgorithmName algorithm, int iterations, int length) + // Write the format marker. + payload[0] = 0x01; + + // Write the hashing algorithm version. + BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(1, 4), algorithm switch { - var key = DeriveKey(secret, salt, algorithm, iterations, length); - var payload = new Span(new byte[13 + salt.Length + key.Length]); + var name when name == HashAlgorithmName.SHA1 => 0, + var name when name == HashAlgorithmName.SHA256 => 1, + var name when name == HashAlgorithmName.SHA512 => 2, + + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)) + }); - // Write the format marker. - payload[0] = 0x01; + // Write the iteration count of the algorithm. + BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(5, 8), (uint) iterations); - // Write the hashing algorithm version. - BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(1, 4), algorithm switch - { - var name when name == HashAlgorithmName.SHA1 => 0, - var name when name == HashAlgorithmName.SHA256 => 1, - var name when name == HashAlgorithmName.SHA512 => 2, + // Write the size of the salt. + BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(9, 12), (uint) salt.Length); - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)) - }); + // Write the salt. + salt.CopyTo(payload.Slice(13)); - // Write the iteration count of the algorithm. - BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(5, 8), (uint) iterations); + // Write the subkey. + key.CopyTo(payload.Slice(13 + salt.Length)); - // Write the size of the salt. - BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(9, 12), (uint) salt.Length); + return payload; + } + } - // Write the salt. - salt.CopyTo(payload.Slice(13)); + /// + /// Validates the specified value to ensure it corresponds to the client secret. + /// Note: when overriding this method, using a time-constant comparer is strongly recommended. + /// + /// The client secret to compare to the value stored in the database. + /// The value stored in the database, which is usually a hashed representation of the secret. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns a boolean indicating whether the specified value was valid. + /// + protected virtual ValueTask ValidateClientSecretAsync( + string secret, string comparand, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(secret)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0216), nameof(secret)); + } - // Write the subkey. - key.CopyTo(payload.Slice(13 + salt.Length)); + if (string.IsNullOrEmpty(comparand)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0218), nameof(comparand)); + } - return payload; - } + try + { + return new ValueTask(VerifyHashedSecret(comparand, secret)); } - /// - /// Validates the specified value to ensure it corresponds to the client secret. - /// Note: when overriding this method, using a time-constant comparer is strongly recommended. - /// - /// The client secret to compare to the value stored in the database. - /// The value stored in the database, which is usually a hashed representation of the secret. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns a boolean indicating whether the specified value was valid. - /// - protected virtual ValueTask ValidateClientSecretAsync( - string secret, string comparand, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(secret)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0216), nameof(secret)); - } + catch (Exception exception) + { + Logger.LogWarning(exception, SR.GetResourceString(SR.ID6163)); - if (string.IsNullOrEmpty(comparand)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0218), nameof(comparand)); - } + return new ValueTask(false); + } - try + // Note: the following logic deliberately uses the same format as CryptoHelper (used in OpenIddict 1.x/2.x), + // which was itself based on ASP.NET Core Identity's latest hashed password format. This guarantees that + // secrets hashed using a recent OpenIddict version can still be read by older packages (and vice versa). + + static bool VerifyHashedSecret(string hash, string secret) + { + var payload = new ReadOnlySpan(Convert.FromBase64String(hash)); + if (payload.Length == 0) { - return new ValueTask(VerifyHashedSecret(comparand, secret)); + return false; } - catch (Exception exception) + // Verify the hashing format version. + if (payload[0] != 0x01) { - Logger.LogWarning(exception, SR.GetResourceString(SR.ID6163)); - - return new ValueTask(false); + return false; } - // Note: the following logic deliberately uses the same format as CryptoHelper (used in OpenIddict 1.x/2.x), - // which was itself based on ASP.NET Core Identity's latest hashed password format. This guarantees that - // secrets hashed using a recent OpenIddict version can still be read by older packages (and vice versa). - - static bool VerifyHashedSecret(string hash, string secret) + // Read the hashing algorithm version. + var algorithm = (int) BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(1, 4)) switch { - var payload = new ReadOnlySpan(Convert.FromBase64String(hash)); - if (payload.Length == 0) - { - return false; - } - - // Verify the hashing format version. - if (payload[0] != 0x01) - { - return false; - } - - // Read the hashing algorithm version. - var algorithm = (int) BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(1, 4)) switch - { - 0 => HashAlgorithmName.SHA1, - 1 => HashAlgorithmName.SHA256, - 2 => HashAlgorithmName.SHA512, + 0 => HashAlgorithmName.SHA1, + 1 => HashAlgorithmName.SHA256, + 2 => HashAlgorithmName.SHA512, - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)) - }; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)) + }; - // Read the iteration count of the algorithm. - var iterations = (int) BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(5, 8)); + // Read the iteration count of the algorithm. + var iterations = (int) BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(5, 8)); - // Read the size of the salt and ensure it's more than 128 bits. - var saltLength = (int) BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(9, 12)); - if (saltLength < 128 / 8) - { - return false; - } + // Read the size of the salt and ensure it's more than 128 bits. + var saltLength = (int) BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(9, 12)); + if (saltLength < 128 / 8) + { + return false; + } - // Read the salt. - var salt = payload.Slice(13, saltLength); + // Read the salt. + var salt = payload.Slice(13, saltLength); - // Ensure the derived key length is more than 128 bits. - var keyLength = payload.Length - 13 - salt.Length; - if (keyLength < 128 / 8) - { - return false; - } + // Ensure the derived key length is more than 128 bits. + var keyLength = payload.Length - 13 - salt.Length; + if (keyLength < 128 / 8) + { + return false; + } #if SUPPORTS_TIME_CONSTANT_COMPARISONS - return CryptographicOperations.FixedTimeEquals( - left: payload.Slice(13 + salt.Length, keyLength), - right: DeriveKey(secret, salt, algorithm, iterations, keyLength)); + return CryptographicOperations.FixedTimeEquals( + left: payload.Slice(13 + salt.Length, keyLength), + right: DeriveKey(secret, salt, algorithm, iterations, keyLength)); #else - return Arrays.ConstantTimeAreEqual( - a: payload.Slice(13 + salt.Length, keyLength).ToArray(), - b: DeriveKey(secret, salt, algorithm, iterations, keyLength)); + return Arrays.ConstantTimeAreEqual( + a: payload.Slice(13 + salt.Length, keyLength).ToArray(), + b: DeriveKey(secret, salt, algorithm, iterations, keyLength)); #endif - } } + } - [SuppressMessage("Security", "CA5379:Do not use weak key derivation function algorithm", - Justification = "The SHA-1 digest algorithm is still supported for backward compatibility.")] - private static byte[] DeriveKey(string secret, ReadOnlySpan salt, - HashAlgorithmName algorithm, int iterations, int length) - { + [SuppressMessage("Security", "CA5379:Do not use weak key derivation function algorithm", + Justification = "The SHA-1 digest algorithm is still supported for backward compatibility.")] + private static byte[] DeriveKey(string secret, ReadOnlySpan salt, + HashAlgorithmName algorithm, int iterations, int length) + { #if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM - using var generator = new Rfc2898DeriveBytes(secret, salt.ToArray(), iterations, algorithm); - return generator.GetBytes(length); + using var generator = new Rfc2898DeriveBytes(secret, salt.ToArray(), iterations, algorithm); + return generator.GetBytes(length); #else - var generator = new Pkcs5S2ParametersGenerator(algorithm switch - { - var name when name == HashAlgorithmName.SHA1 => new Sha1Digest(), - var name when name == HashAlgorithmName.SHA256 => new Sha256Digest(), - var name when name == HashAlgorithmName.SHA512 => new Sha512Digest(), + var generator = new Pkcs5S2ParametersGenerator(algorithm switch + { + var name when name == HashAlgorithmName.SHA1 => new Sha1Digest(), + var name when name == HashAlgorithmName.SHA256 => new Sha256Digest(), + var name when name == HashAlgorithmName.SHA512 => new Sha512Digest(), - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)) - }); + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)) + }); - generator.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(secret.ToCharArray()), salt.ToArray(), iterations); + generator.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(secret.ToCharArray()), salt.ToArray(), iterations); - var key = (KeyParameter) generator.GenerateDerivedMacParameters(length * 8); - return key.GetKey(); + var key = (KeyParameter) generator.GenerateDerivedMacParameters(length * 8); + return key.GetKey(); #endif - } + } - /// - ValueTask IOpenIddictApplicationManager.CountAsync(CancellationToken cancellationToken) - => CountAsync(cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.CountAsync(CancellationToken cancellationToken) + => CountAsync(cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - => CountAsync(query, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) + => CountAsync(query, cancellationToken); - /// - async ValueTask IOpenIddictApplicationManager.CreateAsync(OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) - => await CreateAsync(descriptor, cancellationToken); + /// + async ValueTask IOpenIddictApplicationManager.CreateAsync(OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) + => await CreateAsync(descriptor, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.CreateAsync(object application, CancellationToken cancellationToken) - => CreateAsync((TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.CreateAsync(object application, CancellationToken cancellationToken) + => CreateAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.CreateAsync(object application, string? secret, CancellationToken cancellationToken) - => CreateAsync((TApplication) application, secret, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.CreateAsync(object application, string? secret, CancellationToken cancellationToken) + => CreateAsync((TApplication) application, secret, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.DeleteAsync(object application, CancellationToken cancellationToken) - => DeleteAsync((TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.DeleteAsync(object application, CancellationToken cancellationToken) + => DeleteAsync((TApplication) application, cancellationToken); - /// - async ValueTask IOpenIddictApplicationManager.FindByClientIdAsync(string identifier, CancellationToken cancellationToken) - => await FindByClientIdAsync(identifier, cancellationToken); + /// + async ValueTask IOpenIddictApplicationManager.FindByClientIdAsync(string identifier, CancellationToken cancellationToken) + => await FindByClientIdAsync(identifier, cancellationToken); - /// - async ValueTask IOpenIddictApplicationManager.FindByIdAsync(string identifier, CancellationToken cancellationToken) - => await FindByIdAsync(identifier, cancellationToken); + /// + async ValueTask IOpenIddictApplicationManager.FindByIdAsync(string identifier, CancellationToken cancellationToken) + => await FindByIdAsync(identifier, cancellationToken); - /// - IAsyncEnumerable IOpenIddictApplicationManager.FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken) - => FindByPostLogoutRedirectUriAsync(address, cancellationToken); + /// + IAsyncEnumerable IOpenIddictApplicationManager.FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken) + => FindByPostLogoutRedirectUriAsync(address, cancellationToken); - /// - IAsyncEnumerable IOpenIddictApplicationManager.FindByRedirectUriAsync(string address, CancellationToken cancellationToken) - => FindByRedirectUriAsync(address, cancellationToken); + /// + IAsyncEnumerable IOpenIddictApplicationManager.FindByRedirectUriAsync(string address, CancellationToken cancellationToken) + => FindByRedirectUriAsync(address, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.GetAsync(Func, IQueryable> query, CancellationToken cancellationToken) where TResult : default - => GetAsync(query, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.GetAsync(Func, IQueryable> query, CancellationToken cancellationToken) where TResult : default + => GetAsync(query, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) where TResult : default - => GetAsync(query, state, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) where TResult : default + => GetAsync(query, state, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.GetClientIdAsync(object application, CancellationToken cancellationToken) - => GetClientIdAsync((TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.GetClientIdAsync(object application, CancellationToken cancellationToken) + => GetClientIdAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.GetClientTypeAsync(object application, CancellationToken cancellationToken) - => GetClientTypeAsync((TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.GetClientTypeAsync(object application, CancellationToken cancellationToken) + => GetClientTypeAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.GetConsentTypeAsync(object application, CancellationToken cancellationToken) - => GetConsentTypeAsync((TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.GetConsentTypeAsync(object application, CancellationToken cancellationToken) + => GetConsentTypeAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.GetDisplayNameAsync(object application, CancellationToken cancellationToken) - => GetDisplayNameAsync((TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.GetDisplayNameAsync(object application, CancellationToken cancellationToken) + => GetDisplayNameAsync((TApplication) application, cancellationToken); - /// - ValueTask> IOpenIddictApplicationManager.GetDisplayNamesAsync(object application, CancellationToken cancellationToken) - => GetDisplayNamesAsync((TApplication) application, cancellationToken); + /// + ValueTask> IOpenIddictApplicationManager.GetDisplayNamesAsync(object application, CancellationToken cancellationToken) + => GetDisplayNamesAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.GetIdAsync(object application, CancellationToken cancellationToken) - => GetIdAsync((TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.GetIdAsync(object application, CancellationToken cancellationToken) + => GetIdAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.GetLocalizedDisplayNameAsync(object application, CancellationToken cancellationToken) - => GetLocalizedDisplayNameAsync((TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.GetLocalizedDisplayNameAsync(object application, CancellationToken cancellationToken) + => GetLocalizedDisplayNameAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.GetLocalizedDisplayNameAsync(object application, CultureInfo culture, CancellationToken cancellationToken) - => GetLocalizedDisplayNameAsync((TApplication) application, culture, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.GetLocalizedDisplayNameAsync(object application, CultureInfo culture, CancellationToken cancellationToken) + => GetLocalizedDisplayNameAsync((TApplication) application, culture, cancellationToken); - /// - ValueTask> IOpenIddictApplicationManager.GetPermissionsAsync(object application, CancellationToken cancellationToken) - => GetPermissionsAsync((TApplication) application, cancellationToken); + /// + ValueTask> IOpenIddictApplicationManager.GetPermissionsAsync(object application, CancellationToken cancellationToken) + => GetPermissionsAsync((TApplication) application, cancellationToken); - /// - ValueTask> IOpenIddictApplicationManager.GetPostLogoutRedirectUrisAsync(object application, CancellationToken cancellationToken) - => GetPostLogoutRedirectUrisAsync((TApplication) application, cancellationToken); + /// + ValueTask> IOpenIddictApplicationManager.GetPostLogoutRedirectUrisAsync(object application, CancellationToken cancellationToken) + => GetPostLogoutRedirectUrisAsync((TApplication) application, cancellationToken); - /// - ValueTask> IOpenIddictApplicationManager.GetPropertiesAsync(object application, CancellationToken cancellationToken) - => GetPropertiesAsync((TApplication) application, cancellationToken); + /// + ValueTask> IOpenIddictApplicationManager.GetPropertiesAsync(object application, CancellationToken cancellationToken) + => GetPropertiesAsync((TApplication) application, cancellationToken); - /// - ValueTask> IOpenIddictApplicationManager.GetRedirectUrisAsync(object application, CancellationToken cancellationToken) - => GetRedirectUrisAsync((TApplication) application, cancellationToken); + /// + ValueTask> IOpenIddictApplicationManager.GetRedirectUrisAsync(object application, CancellationToken cancellationToken) + => GetRedirectUrisAsync((TApplication) application, cancellationToken); - /// - ValueTask> IOpenIddictApplicationManager.GetRequirementsAsync(object application, CancellationToken cancellationToken) - => GetRequirementsAsync((TApplication) application, cancellationToken); + /// + ValueTask> IOpenIddictApplicationManager.GetRequirementsAsync(object application, CancellationToken cancellationToken) + => GetRequirementsAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.HasClientTypeAsync(object application, string type, CancellationToken cancellationToken) - => HasClientTypeAsync((TApplication) application, type, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.HasClientTypeAsync(object application, string type, CancellationToken cancellationToken) + => HasClientTypeAsync((TApplication) application, type, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.HasConsentTypeAsync(object application, string type, CancellationToken cancellationToken) - => HasConsentTypeAsync((TApplication) application, type, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.HasConsentTypeAsync(object application, string type, CancellationToken cancellationToken) + => HasConsentTypeAsync((TApplication) application, type, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.HasPermissionAsync(object application, string permission, CancellationToken cancellationToken) - => HasPermissionAsync((TApplication) application, permission, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.HasPermissionAsync(object application, string permission, CancellationToken cancellationToken) + => HasPermissionAsync((TApplication) application, permission, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.HasRequirementAsync(object application, string requirement, CancellationToken cancellationToken) - => HasRequirementAsync((TApplication) application, requirement, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.HasRequirementAsync(object application, string requirement, CancellationToken cancellationToken) + => HasRequirementAsync((TApplication) application, requirement, cancellationToken); - /// - IAsyncEnumerable IOpenIddictApplicationManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) - => ListAsync(count, offset, cancellationToken); + /// + IAsyncEnumerable IOpenIddictApplicationManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) + => ListAsync(count, offset, cancellationToken); - /// - IAsyncEnumerable IOpenIddictApplicationManager.ListAsync(Func, IQueryable> query, CancellationToken cancellationToken) - => ListAsync(query, cancellationToken); + /// + IAsyncEnumerable IOpenIddictApplicationManager.ListAsync(Func, IQueryable> query, CancellationToken cancellationToken) + => ListAsync(query, cancellationToken); - /// - IAsyncEnumerable IOpenIddictApplicationManager.ListAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) - => ListAsync(query, state, cancellationToken); + /// + IAsyncEnumerable IOpenIddictApplicationManager.ListAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) + => ListAsync(query, state, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.PopulateAsync(OpenIddictApplicationDescriptor descriptor, object application, CancellationToken cancellationToken) - => PopulateAsync(descriptor, (TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.PopulateAsync(OpenIddictApplicationDescriptor descriptor, object application, CancellationToken cancellationToken) + => PopulateAsync(descriptor, (TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.PopulateAsync(object application, OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) - => PopulateAsync((TApplication) application, descriptor, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.PopulateAsync(object application, OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) + => PopulateAsync((TApplication) application, descriptor, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.UpdateAsync(object application, CancellationToken cancellationToken) - => UpdateAsync((TApplication) application, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.UpdateAsync(object application, CancellationToken cancellationToken) + => UpdateAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.UpdateAsync(object application, OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) - => UpdateAsync((TApplication) application, descriptor, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.UpdateAsync(object application, OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken) + => UpdateAsync((TApplication) application, descriptor, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.UpdateAsync(object application, string? secret, CancellationToken cancellationToken) - => UpdateAsync((TApplication) application, secret, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.UpdateAsync(object application, string? secret, CancellationToken cancellationToken) + => UpdateAsync((TApplication) application, secret, cancellationToken); - /// - IAsyncEnumerable IOpenIddictApplicationManager.ValidateAsync(object application, CancellationToken cancellationToken) - => ValidateAsync((TApplication) application, cancellationToken); + /// + IAsyncEnumerable IOpenIddictApplicationManager.ValidateAsync(object application, CancellationToken cancellationToken) + => ValidateAsync((TApplication) application, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken) - => ValidateClientSecretAsync((TApplication) application, secret, cancellationToken); + /// + ValueTask IOpenIddictApplicationManager.ValidateClientSecretAsync(object application, string secret, CancellationToken cancellationToken) + => ValidateClientSecretAsync((TApplication) application, secret, cancellationToken); - /// - ValueTask IOpenIddictApplicationManager.ValidateRedirectUriAsync(object application, string address, CancellationToken cancellationToken) - => ValidateRedirectUriAsync((TApplication) application, address, cancellationToken); - } -} \ No newline at end of file + /// + ValueTask IOpenIddictApplicationManager.ValidateRedirectUriAsync(object application, string address, CancellationToken cancellationToken) + => ValidateRedirectUriAsync((TApplication) application, address, cancellationToken); +} diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index 7beaf425..ef0bd904 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -22,1314 +22,1313 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Abstractions.OpenIddictExceptions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Provides methods allowing to manage the authorizations stored in the store. +/// +/// +/// Applications that do not want to depend on a specific entity type can use the non-generic +/// instead, for which the actual entity type +/// is resolved at runtime based on the default entity type registered in the core options. +/// +/// The type of the Authorization entity. +public class OpenIddictAuthorizationManager : IOpenIddictAuthorizationManager where TAuthorization : class { + public OpenIddictAuthorizationManager( + IOpenIddictAuthorizationCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictAuthorizationStoreResolver resolver) + { + Cache = cache; + Logger = logger; + Options = options; + Store = resolver.Get(); + } + /// - /// Provides methods allowing to manage the authorizations stored in the store. + /// Gets the cache associated with the current manager. /// - /// - /// Applications that do not want to depend on a specific entity type can use the non-generic - /// instead, for which the actual entity type - /// is resolved at runtime based on the default entity type registered in the core options. - /// - /// The type of the Authorization entity. - public class OpenIddictAuthorizationManager : IOpenIddictAuthorizationManager where TAuthorization : class + protected IOpenIddictAuthorizationCache Cache { get; } + + /// + /// Gets the logger associated with the current manager. + /// + protected ILogger Logger { get; } + + /// + /// Gets the options associated with the current manager. + /// + protected IOptionsMonitor Options { get; } + + /// + /// Gets the store associated with the current manager. + /// + protected IOpenIddictAuthorizationStore Store { get; } + + /// + /// Determines the number of authorizations that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of authorizations in the database. + /// + public virtual ValueTask CountAsync(CancellationToken cancellationToken = default) + => Store.CountAsync(cancellationToken); + + /// + /// Determines the number of authorizations that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of authorizations that match the specified query. + /// + public virtual ValueTask CountAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) { - public OpenIddictAuthorizationManager( - IOpenIddictAuthorizationCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictAuthorizationStoreResolver resolver) - { - Cache = cache; - Logger = logger; - Options = options; - Store = resolver.Get(); - } - - /// - /// Gets the cache associated with the current manager. - /// - protected IOpenIddictAuthorizationCache Cache { get; } - - /// - /// Gets the logger associated with the current manager. - /// - protected ILogger Logger { get; } - - /// - /// Gets the options associated with the current manager. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the store associated with the current manager. - /// - protected IOpenIddictAuthorizationStore Store { get; } - - /// - /// Determines the number of authorizations that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of authorizations in the database. - /// - public virtual ValueTask CountAsync(CancellationToken cancellationToken = default) - => Store.CountAsync(cancellationToken); - - /// - /// Determines the number of authorizations that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of authorizations that match the specified query. - /// - public virtual ValueTask CountAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } + + return Store.CountAsync(query, cancellationToken); + } - return Store.CountAsync(query, cancellationToken); + /// + /// Creates a new authorization. + /// + /// The application to create. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Creates a new authorization. - /// - /// The application to create. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken = default) + // If no status was explicitly specified, assume that the authorization is valid. + if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + await Store.SetStatusAsync(authorization, Statuses.Valid, cancellationToken); + } + + var results = await GetValidationResultsAsync(authorization, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) + { + var builder = new StringBuilder(); + builder.AppendLine(SR.GetResourceString(SR.ID0219)); + builder.AppendLine(); - // If no status was explicitly specified, assume that the authorization is valid. - if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) + foreach (var result in results) { - await Store.SetStatusAsync(authorization, Statuses.Valid, cancellationToken); + builder.AppendLine(result.ErrorMessage); } - var results = await GetValidationResultsAsync(authorization, cancellationToken); - if (results.Any(result => result != ValidationResult.Success)) - { - var builder = new StringBuilder(); - builder.AppendLine(SR.GetResourceString(SR.ID0219)); - builder.AppendLine(); + throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); + } - foreach (var result in results) - { - builder.AppendLine(result.ErrorMessage); - } + await Store.CreateAsync(authorization, cancellationToken); - throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); - } + if (!Options.CurrentValue.DisableEntityCaching) + { + await Cache.AddAsync(authorization, cancellationToken); + } - await Store.CreateAsync(authorization, cancellationToken); + async Task> GetValidationResultsAsync( + TAuthorization authorization, CancellationToken cancellationToken) + { + var builder = ImmutableArray.CreateBuilder(); - if (!Options.CurrentValue.DisableEntityCaching) + await foreach (var result in ValidateAsync(authorization, cancellationToken)) { - await Cache.AddAsync(authorization, cancellationToken); + builder.Add(result); } - async Task> GetValidationResultsAsync( - TAuthorization authorization, CancellationToken cancellationToken) - { - var builder = ImmutableArray.CreateBuilder(); + return builder.ToImmutable(); + } + } - await foreach (var result in ValidateAsync(authorization, cancellationToken)) - { - builder.Add(result); - } + /// + /// Creates a new authorization based on the specified descriptor. + /// + /// The authorization descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. + /// + public virtual async ValueTask CreateAsync( + OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (descriptor is null) + { + throw new ArgumentNullException(nameof(descriptor)); + } - return builder.ToImmutable(); - } + var authorization = await Store.InstantiateAsync(cancellationToken); + if (authorization is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0220)); } - /// - /// Creates a new authorization based on the specified descriptor. - /// - /// The authorization descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. - /// - public virtual async ValueTask CreateAsync( - OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) + await PopulateAsync(authorization, descriptor, cancellationToken); + await CreateAsync(authorization, cancellationToken); + + return authorization; + } + + /// + /// Creates a new permanent authorization based on the specified parameters. + /// + /// The principal associated with the authorization. + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization type. + /// The minimal scopes associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. + /// + public virtual ValueTask CreateAsync( + ClaimsPrincipal principal, string subject, string client, + string type, ImmutableArray scopes, CancellationToken cancellationToken = default) + { + if (principal is null) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + throw new ArgumentNullException(nameof(principal)); + } - var authorization = await Store.InstantiateAsync(cancellationToken); - if (authorization is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0220)); - } + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - await PopulateAsync(authorization, descriptor, cancellationToken); - await CreateAsync(authorization, cancellationToken); - - return authorization; - } - - /// - /// Creates a new permanent authorization based on the specified parameters. - /// - /// The principal associated with the authorization. - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization type. - /// The minimal scopes associated with the authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the authorization. - /// - public virtual ValueTask CreateAsync( - ClaimsPrincipal principal, string subject, string client, - string type, ImmutableArray scopes, CancellationToken cancellationToken = default) - { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + var descriptor = new OpenIddictAuthorizationDescriptor + { + ApplicationId = client, + CreationDate = DateTimeOffset.UtcNow, + Principal = principal, + Status = Statuses.Valid, + Subject = subject, + Type = type + }; - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + descriptor.Scopes.UnionWith(scopes); - var descriptor = new OpenIddictAuthorizationDescriptor - { - ApplicationId = client, - CreationDate = DateTimeOffset.UtcNow, - Principal = principal, - Status = Statuses.Valid, - Subject = subject, - Type = type - }; - - descriptor.Scopes.UnionWith(scopes); - - return CreateAsync(descriptor, cancellationToken); - } - - /// - /// Removes an existing authorization. - /// - /// The authorization to delete. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return CreateAsync(descriptor, cancellationToken); + } - if (!Options.CurrentValue.DisableEntityCaching) - { - await Cache.RemoveAsync(authorization, cancellationToken); - } + /// + /// Removes an existing authorization. + /// + /// The authorization to delete. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - await Store.DeleteAsync(authorization, cancellationToken); + if (!Options.CurrentValue.DisableEntityCaching) + { + await Cache.RemoveAsync(authorization, cancellationToken); } - /// - /// Retrieves the authorizations corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the subject/client. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, CancellationToken cancellationToken = default) + await Store.DeleteAsync(authorization, cancellationToken); + } + + /// + /// Retrieves the authorizations corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the subject/client. + public virtual IAsyncEnumerable FindAsync( + string subject, string client, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - var authorizations = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, cancellationToken) : - Cache.FindAsync(subject, client, cancellationToken); + var authorizations = Options.CurrentValue.DisableEntityCaching ? + Store.FindAsync(subject, client, cancellationToken) : + Cache.FindAsync(subject, client, cancellationToken); - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return authorizations; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return authorizations; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var authorization in authorizations) { - await foreach (var authorization in authorizations) + if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return authorization; - } + yield return authorization; } } } + } - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken = default) + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - var authorizations = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, status, cancellationToken) : - Cache.FindAsync(subject, client, status, cancellationToken); + var authorizations = Options.CurrentValue.DisableEntityCaching ? + Store.FindAsync(subject, client, status, cancellationToken) : + Cache.FindAsync(subject, client, status, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return authorizations; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return authorizations; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var authorization in authorizations) { - await foreach (var authorization in authorizations) + if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return authorization; - } + yield return authorization; } } } + } - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - var authorizations = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, status, type, cancellationToken) : - Cache.FindAsync(subject, client, status, type, cancellationToken); + var authorizations = Options.CurrentValue.DisableEntityCaching ? + Store.FindAsync(subject, client, status, type, cancellationToken) : + Cache.FindAsync(subject, client, status, type, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return authorizations; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return authorizations; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var authorization in authorizations) { - await foreach (var authorization in authorizations) + if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return authorization; - } + yield return authorization; } } } + } - /// - /// Retrieves the authorizations matching the specified parameters. - /// - /// The subject associated with the authorization. - /// The client associated with the authorization. - /// The authorization status. - /// The authorization type. - /// The minimal scopes associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the criteria. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + /// + /// Retrieves the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The minimal scopes associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the criteria. + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, + ImmutableArray scopes, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - var authorizations = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, status, type, scopes, cancellationToken) : - Cache.FindAsync(subject, client, status, type, scopes, cancellationToken); + var authorizations = Options.CurrentValue.DisableEntityCaching ? + Store.FindAsync(subject, client, status, type, scopes, cancellationToken) : + Cache.FindAsync(subject, client, status, type, scopes, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return authorizations; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return authorizations; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var authorization in authorizations) { - await foreach (var authorization in authorizations) + if (!string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { - if (!string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) - { - continue; - } - - if (!await HasScopesAsync(authorization, scopes, cancellationToken)) - { - continue; - } + continue; + } - yield return authorization; + if (!await HasScopesAsync(authorization, scopes, cancellationToken)) + { + continue; } + + yield return authorization; } } + } - /// - /// Retrieves the list of authorizations corresponding to the specified application identifier. - /// - /// The application identifier associated with the authorizations. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the specified application. - public virtual IAsyncEnumerable FindByApplicationIdAsync( - string identifier, CancellationToken cancellationToken = default) + /// + /// Retrieves the list of authorizations corresponding to the specified application identifier. + /// + /// The application identifier associated with the authorizations. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the specified application. + public virtual IAsyncEnumerable FindByApplicationIdAsync( + string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - var authorizations = Options.CurrentValue.DisableEntityCaching ? - Store.FindByApplicationIdAsync(identifier, cancellationToken) : - Cache.FindByApplicationIdAsync(identifier, cancellationToken); + var authorizations = Options.CurrentValue.DisableEntityCaching ? + Store.FindByApplicationIdAsync(identifier, cancellationToken) : + Cache.FindByApplicationIdAsync(identifier, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return authorizations; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return authorizations; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var authorization in authorizations) { - await foreach (var authorization in authorizations) + if (string.Equals(await Store.GetApplicationIdAsync(authorization, cancellationToken), identifier, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetApplicationIdAsync(authorization, cancellationToken), identifier, StringComparison.Ordinal)) - { - yield return authorization; - } + yield return authorization; } } } + } - /// - /// Retrieves an authorization using its unique identifier. - /// - /// The unique identifier associated with the authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization corresponding to the identifier. - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default) + /// + /// Retrieves an authorization using its unique identifier. + /// + /// The unique identifier associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorization corresponding to the identifier. + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } - - var authorization = Options.CurrentValue.DisableEntityCaching ? - await Store.FindByIdAsync(identifier, cancellationToken) : - await Cache.FindByIdAsync(identifier, cancellationToken); + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - if (authorization is null) - { - return null; - } + var authorization = Options.CurrentValue.DisableEntityCaching ? + await Store.FindByIdAsync(identifier, cancellationToken) : + await Cache.FindByIdAsync(identifier, cancellationToken); - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - if (!Options.CurrentValue.DisableAdditionalFiltering && - !string.Equals(await Store.GetIdAsync(authorization, cancellationToken), identifier, StringComparison.Ordinal)) - { - return null; - } + if (authorization is null) + { + return null; + } - return authorization; + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + if (!Options.CurrentValue.DisableAdditionalFiltering && + !string.Equals(await Store.GetIdAsync(authorization, cancellationToken), identifier, StringComparison.Ordinal)) + { + return null; } - /// - /// Retrieves all the authorizations corresponding to the specified subject. - /// - /// The subject associated with the authorization. - /// The that can be used to abort the operation. - /// The authorizations corresponding to the specified subject. - public virtual IAsyncEnumerable FindBySubjectAsync( - string subject, CancellationToken cancellationToken = default) + return authorization; + } + + /// + /// Retrieves all the authorizations corresponding to the specified subject. + /// + /// The subject associated with the authorization. + /// The that can be used to abort the operation. + /// The authorizations corresponding to the specified subject. + public virtual IAsyncEnumerable FindBySubjectAsync( + string subject, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - var authorizations = Options.CurrentValue.DisableEntityCaching ? - Store.FindBySubjectAsync(subject, cancellationToken) : - Cache.FindBySubjectAsync(subject, cancellationToken); + var authorizations = Options.CurrentValue.DisableEntityCaching ? + Store.FindBySubjectAsync(subject, cancellationToken) : + Cache.FindBySubjectAsync(subject, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return authorizations; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return authorizations; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var authorization in authorizations) { - await foreach (var authorization in authorizations) + if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return authorization; - } + yield return authorization; } } } + } - /// - /// Retrieves the optional application identifier associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the application identifier associated with the authorization. - /// - public virtual ValueTask GetApplicationIdAsync( - TAuthorization authorization, CancellationToken cancellationToken = default) + /// + /// Retrieves the optional application identifier associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the application identifier associated with the authorization. + /// + public virtual ValueTask GetApplicationIdAsync( + TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - return Store.GetApplicationIdAsync(authorization, cancellationToken); + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Executes the specified query and returns the first element. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - public virtual ValueTask GetAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) + return Store.GetApplicationIdAsync(authorization, cancellationToken); + } + + /// + /// Executes the specified query and returns the first element. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + public virtual ValueTask GetAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(query)); + } - return GetAsync(static (authorizations, query) => query(authorizations), query, cancellationToken); - } - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - public virtual ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + return GetAsync(static (authorizations, query) => query(authorizations), query, cancellationToken); + } - return Store.GetAsync(query, state, cancellationToken); + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + public virtual ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - /// Retrieves the creation date associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the creation date associated with the specified authorization. - /// - public virtual ValueTask GetCreationDateAsync( - TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return Store.GetAsync(query, state, cancellationToken); + } - return Store.GetCreationDateAsync(authorization, cancellationToken); + /// + /// Retrieves the creation date associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the creation date associated with the specified authorization. + /// + public virtual ValueTask GetCreationDateAsync( + TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Retrieves the unique identifier associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the authorization. - /// - public virtual ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return Store.GetCreationDateAsync(authorization, cancellationToken); + } - return Store.GetIdAsync(authorization, cancellationToken); + /// + /// Retrieves the unique identifier associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the authorization. + /// + public virtual ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Retrieves the additional properties associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the authorization. - /// - public virtual ValueTask> GetPropertiesAsync( - TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return Store.GetIdAsync(authorization, cancellationToken); + } - return Store.GetPropertiesAsync(authorization, cancellationToken); + /// + /// Retrieves the additional properties associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the authorization. + /// + public virtual ValueTask> GetPropertiesAsync( + TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Retrieves the scopes associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scopes associated with the specified authorization. - /// - public virtual ValueTask> GetScopesAsync( - TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return Store.GetPropertiesAsync(authorization, cancellationToken); + } - return Store.GetScopesAsync(authorization, cancellationToken); + /// + /// Retrieves the scopes associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scopes associated with the specified authorization. + /// + public virtual ValueTask> GetScopesAsync( + TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Retrieves the status associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the status associated with the specified authorization. - /// - public virtual ValueTask GetStatusAsync( - TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return Store.GetScopesAsync(authorization, cancellationToken); + } - return Store.GetStatusAsync(authorization, cancellationToken); + /// + /// Retrieves the status associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the status associated with the specified authorization. + /// + public virtual ValueTask GetStatusAsync( + TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Retrieves the subject associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the subject associated with the specified authorization. - /// - public virtual ValueTask GetSubjectAsync( - TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return Store.GetStatusAsync(authorization, cancellationToken); + } - return Store.GetSubjectAsync(authorization, cancellationToken); + /// + /// Retrieves the subject associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the subject associated with the specified authorization. + /// + public virtual ValueTask GetSubjectAsync( + TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Retrieves the type associated with an authorization. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the type associated with the specified authorization. - /// - public virtual ValueTask GetTypeAsync( - TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return Store.GetSubjectAsync(authorization, cancellationToken); + } - return Store.GetTypeAsync(authorization, cancellationToken); + /// + /// Retrieves the type associated with an authorization. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the type associated with the specified authorization. + /// + public virtual ValueTask GetTypeAsync( + TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Determines whether the specified scopes are included in the authorization. - /// - /// The authorization. - /// The scopes. - /// The that can be used to abort the operation. - /// true if the scopes are included in the authorization, false otherwise. - public virtual async ValueTask HasScopesAsync(TAuthorization authorization, - ImmutableArray scopes, CancellationToken cancellationToken = default) + return Store.GetTypeAsync(authorization, cancellationToken); + } + + /// + /// Determines whether the specified scopes are included in the authorization. + /// + /// The authorization. + /// The scopes. + /// The that can be used to abort the operation. + /// true if the scopes are included in the authorization, false otherwise. + public virtual async ValueTask HasScopesAsync(TAuthorization authorization, + ImmutableArray scopes, CancellationToken cancellationToken = default) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } + + return new HashSet(await Store.GetScopesAsync( + authorization, cancellationToken), StringComparer.Ordinal).IsSupersetOf(scopes); + } - return new HashSet(await Store.GetScopesAsync( - authorization, cancellationToken), StringComparer.Ordinal).IsSupersetOf(scopes); + /// + /// Determines whether a given authorization has the specified status. + /// + /// The authorization. + /// The expected status. + /// The that can be used to abort the operation. + /// true if the authorization has the specified status, false otherwise. + public virtual async ValueTask HasStatusAsync(TAuthorization authorization, + string status, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Determines whether a given authorization has the specified status. - /// - /// The authorization. - /// The expected status. - /// The that can be used to abort the operation. - /// true if the authorization has the specified status, false otherwise. - public virtual async ValueTask HasStatusAsync(TAuthorization authorization, - string status, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(status)) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + return string.Equals(await Store.GetStatusAsync(authorization, cancellationToken), status, StringComparison.OrdinalIgnoreCase); + } - return string.Equals(await Store.GetStatusAsync(authorization, cancellationToken), status, StringComparison.OrdinalIgnoreCase); + /// + /// Determines whether a given authorization has the specified type. + /// + /// The authorization. + /// The expected type. + /// The that can be used to abort the operation. + /// true if the authorization has the specified type, false otherwise. + public virtual async ValueTask HasTypeAsync( + TAuthorization authorization, string type, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Determines whether a given authorization has the specified type. - /// - /// The authorization. - /// The expected type. - /// The that can be used to abort the operation. - /// true if the authorization has the specified type, false otherwise. - public virtual async ValueTask HasTypeAsync( - TAuthorization authorization, string type, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(type)) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + return string.Equals(await Store.GetTypeAsync(authorization, cancellationToken), type, StringComparison.OrdinalIgnoreCase); + } - return string.Equals(await Store.GetTypeAsync(authorization, cancellationToken), type, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - int? count = null, int? offset = null, CancellationToken cancellationToken = default) - => Store.ListAsync(count, offset, cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + int? count = null, int? offset = null, CancellationToken cancellationToken = default) + => Store.ListAsync(count, offset, cancellationToken); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } - return ListAsync(static (authorizations, query) => query(authorizations), query, cancellationToken); + return ListAsync(static (authorizations, query) => query(authorizations), query, cancellationToken); + } + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default) + return Store.ListAsync(query, state, cancellationToken); + } + + /// + /// Populates the authorization using the specified descriptor. + /// + /// The authorization. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask PopulateAsync(TAuthorization authorization, + OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (authorization is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(authorization)); + } - return Store.ListAsync(query, state, cancellationToken); + if (descriptor is null) + { + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Populates the authorization using the specified descriptor. - /// - /// The authorization. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask PopulateAsync(TAuthorization authorization, - OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) + await Store.SetApplicationIdAsync(authorization, descriptor.ApplicationId, cancellationToken); + await Store.SetCreationDateAsync(authorization, descriptor.CreationDate, cancellationToken); + await Store.SetPropertiesAsync(authorization, descriptor.Properties.ToImmutableDictionary(), cancellationToken); + await Store.SetScopesAsync(authorization, descriptor.Scopes.ToImmutableArray(), cancellationToken); + await Store.SetStatusAsync(authorization, descriptor.Status, cancellationToken); + await Store.SetSubjectAsync(authorization, descriptor.Subject, cancellationToken); + await Store.SetTypeAsync(authorization, descriptor.Type, cancellationToken); + } + + /// + /// Populates the specified descriptor using the properties exposed by the authorization. + /// + /// The descriptor. + /// The authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask PopulateAsync( + OpenIddictAuthorizationDescriptor descriptor, + TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (descriptor is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - await Store.SetApplicationIdAsync(authorization, descriptor.ApplicationId, cancellationToken); - await Store.SetCreationDateAsync(authorization, descriptor.CreationDate, cancellationToken); - await Store.SetPropertiesAsync(authorization, descriptor.Properties.ToImmutableDictionary(), cancellationToken); - await Store.SetScopesAsync(authorization, descriptor.Scopes.ToImmutableArray(), cancellationToken); - await Store.SetStatusAsync(authorization, descriptor.Status, cancellationToken); - await Store.SetSubjectAsync(authorization, descriptor.Subject, cancellationToken); - await Store.SetTypeAsync(authorization, descriptor.Type, cancellationToken); - } - - /// - /// Populates the specified descriptor using the properties exposed by the authorization. - /// - /// The descriptor. - /// The authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask PopulateAsync( - OpenIddictAuthorizationDescriptor descriptor, - TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + descriptor.ApplicationId = await Store.GetApplicationIdAsync(authorization, cancellationToken); + descriptor.CreationDate = await Store.GetCreationDateAsync(authorization, cancellationToken); + descriptor.Scopes.Clear(); + descriptor.Scopes.UnionWith(await Store.GetScopesAsync(authorization, cancellationToken)); + descriptor.Status = await Store.GetStatusAsync(authorization, cancellationToken); + descriptor.Subject = await Store.GetSubjectAsync(authorization, cancellationToken); + descriptor.Type = await Store.GetTypeAsync(authorization, cancellationToken); - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + descriptor.Properties.Clear(); + foreach (var pair in await Store.GetPropertiesAsync(authorization, cancellationToken)) + { + descriptor.Properties.Add(pair.Key, pair.Value); + } + } - descriptor.ApplicationId = await Store.GetApplicationIdAsync(authorization, cancellationToken); - descriptor.CreationDate = await Store.GetCreationDateAsync(authorization, cancellationToken); - descriptor.Scopes.Clear(); - descriptor.Scopes.UnionWith(await Store.GetScopesAsync(authorization, cancellationToken)); - descriptor.Status = await Store.GetStatusAsync(authorization, cancellationToken); - descriptor.Subject = await Store.GetSubjectAsync(authorization, cancellationToken); - descriptor.Type = await Store.GetTypeAsync(authorization, cancellationToken); + /// + /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached. + /// Only authorizations created before the specified are removed. + /// + /// + /// To ensure ad-hoc authorizations that no longer have any valid/non-expired token + /// attached are correctly removed, the tokens should always be pruned first. + /// + /// The date before which authorizations are not pruned. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default) + => Store.PruneAsync(threshold, cancellationToken); - descriptor.Properties.Clear(); - foreach (var pair in await Store.GetPropertiesAsync(authorization, cancellationToken)) - { - descriptor.Properties.Add(pair.Key, pair.Value); - } + /// + /// Tries to revoke an authorization. + /// + /// The authorization to revoke. + /// The that can be used to abort the operation. + /// true if the authorization was successfully revoked, false otherwise. + public virtual async ValueTask TryRevokeAsync(TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached. - /// Only authorizations created before the specified are removed. - /// - /// - /// To ensure ad-hoc authorizations that no longer have any valid/non-expired token - /// attached are correctly removed, the tokens should always be pruned first. - /// - /// The date before which authorizations are not pruned. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default) - => Store.PruneAsync(threshold, cancellationToken); - - /// - /// Tries to revoke an authorization. - /// - /// The authorization to revoke. - /// The that can be used to abort the operation. - /// true if the authorization was successfully revoked, false otherwise. - public virtual async ValueTask TryRevokeAsync(TAuthorization authorization, CancellationToken cancellationToken = default) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + var status = await Store.GetStatusAsync(authorization, cancellationToken); + if (string.Equals(status, Statuses.Revoked, StringComparison.OrdinalIgnoreCase)) + { + return true; + } - var status = await Store.GetStatusAsync(authorization, cancellationToken); - if (string.Equals(status, Statuses.Revoked, StringComparison.OrdinalIgnoreCase)) - { - return true; - } + await Store.SetStatusAsync(authorization, Statuses.Revoked, cancellationToken); - await Store.SetStatusAsync(authorization, Statuses.Revoked, cancellationToken); + try + { + await UpdateAsync(authorization, cancellationToken); - try - { - await UpdateAsync(authorization, cancellationToken); + Logger.LogInformation(SR.GetResourceString(SR.ID6164), await Store.GetIdAsync(authorization, cancellationToken)); - Logger.LogInformation(SR.GetResourceString(SR.ID6164), await Store.GetIdAsync(authorization, cancellationToken)); + return true; + } - return true; - } + catch (ConcurrencyException exception) + { + Logger.LogDebug(exception, SR.GetResourceString(SR.ID6165), await Store.GetIdAsync(authorization, cancellationToken)); - catch (ConcurrencyException exception) - { - Logger.LogDebug(exception, SR.GetResourceString(SR.ID6165), await Store.GetIdAsync(authorization, cancellationToken)); + return false; + } - return false; - } + catch (Exception exception) + { + Logger.LogWarning(exception, SR.GetResourceString(SR.ID6166), await Store.GetIdAsync(authorization, cancellationToken)); - catch (Exception exception) - { - Logger.LogWarning(exception, SR.GetResourceString(SR.ID6166), await Store.GetIdAsync(authorization, cancellationToken)); + return false; + } + } - return false; - } + /// + /// Updates an existing authorization. + /// + /// The authorization to update. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Updates an existing authorization. - /// - /// The authorization to update. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken = default) + var results = await GetValidationResultsAsync(authorization, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - if (authorization is null) + var builder = new StringBuilder(); + builder.AppendLine(SR.GetResourceString(SR.ID0221)); + builder.AppendLine(); + + foreach (var result in results) { - throw new ArgumentNullException(nameof(authorization)); + builder.AppendLine(result.ErrorMessage); } - var results = await GetValidationResultsAsync(authorization, cancellationToken); - if (results.Any(result => result != ValidationResult.Success)) - { - var builder = new StringBuilder(); - builder.AppendLine(SR.GetResourceString(SR.ID0221)); - builder.AppendLine(); + throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); + } - foreach (var result in results) - { - builder.AppendLine(result.ErrorMessage); - } + await Store.UpdateAsync(authorization, cancellationToken); - throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); - } + if (!Options.CurrentValue.DisableEntityCaching) + { + await Cache.RemoveAsync(authorization, cancellationToken); + await Cache.AddAsync(authorization, cancellationToken); + } - await Store.UpdateAsync(authorization, cancellationToken); + async Task> GetValidationResultsAsync( + TAuthorization authorization, CancellationToken cancellationToken) + { + var builder = ImmutableArray.CreateBuilder(); - if (!Options.CurrentValue.DisableEntityCaching) + await foreach (var result in ValidateAsync(authorization, cancellationToken)) { - await Cache.RemoveAsync(authorization, cancellationToken); - await Cache.AddAsync(authorization, cancellationToken); + builder.Add(result); } - async Task> GetValidationResultsAsync( - TAuthorization authorization, CancellationToken cancellationToken) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var result in ValidateAsync(authorization, cancellationToken)) - { - builder.Add(result); - } + return builder.ToImmutable(); + } + } - return builder.ToImmutable(); - } + /// + /// Updates an existing authorization. + /// + /// The authorization to update. + /// The descriptor used to update the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask UpdateAsync(TAuthorization authorization, + OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Updates an existing authorization. - /// - /// The authorization to update. - /// The descriptor used to update the authorization. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask UpdateAsync(TAuthorization authorization, - OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) + if (descriptor is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + await PopulateAsync(authorization, descriptor, cancellationToken); + await UpdateAsync(authorization, cancellationToken); + } - await PopulateAsync(authorization, descriptor, cancellationToken); - await UpdateAsync(authorization, cancellationToken); + /// + /// Validates the authorization to ensure it's in a consistent state. + /// + /// The authorization. + /// The that can be used to abort the operation. + /// The validation error encountered when validating the authorization. + public virtual async IAsyncEnumerable ValidateAsync( + TAuthorization authorization, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - /// Validates the authorization to ensure it's in a consistent state. - /// - /// The authorization. - /// The that can be used to abort the operation. - /// The validation error encountered when validating the authorization. - public virtual async IAsyncEnumerable ValidateAsync( - TAuthorization authorization, [EnumeratorCancellation] CancellationToken cancellationToken = default) + var type = await Store.GetTypeAsync(authorization, cancellationToken); + if (string.IsNullOrEmpty(type)) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + yield return new ValidationResult(SR.GetResourceString(SR.ID2116)); + } - var type = await Store.GetTypeAsync(authorization, cancellationToken); - if (string.IsNullOrEmpty(type)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2116)); - } + else if (!string.Equals(type, AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) && + !string.Equals(type, AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase)) + { + yield return new ValidationResult(SR.GetResourceString(SR.ID2117)); + } - else if (!string.Equals(type, AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) && - !string.Equals(type, AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2117)); - } + if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) + { + yield return new ValidationResult(SR.GetResourceString(SR.ID2038)); + } - if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) + // Ensure that the scopes are not null or empty and do not contain spaces. + foreach (var scope in await Store.GetScopesAsync(authorization, cancellationToken)) + { + if (string.IsNullOrEmpty(scope)) { - yield return new ValidationResult(SR.GetResourceString(SR.ID2038)); + yield return new ValidationResult(SR.GetResourceString(SR.ID2039)); + + break; } - // Ensure that the scopes are not null or empty and do not contain spaces. - foreach (var scope in await Store.GetScopesAsync(authorization, cancellationToken)) + if (scope.Contains(Separators.Space[0])) { - if (string.IsNullOrEmpty(scope)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2039)); + yield return new ValidationResult(SR.GetResourceString(SR.ID2042)); - break; - } - - if (scope.Contains(Separators.Space[0])) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2042)); - - break; - } + break; } } + } - /// - ValueTask IOpenIddictAuthorizationManager.CountAsync(CancellationToken cancellationToken) - => CountAsync(cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.CountAsync(CancellationToken cancellationToken) + => CountAsync(cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - => CountAsync(query, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) + => CountAsync(query, cancellationToken); - /// - async ValueTask IOpenIddictAuthorizationManager.CreateAsync(ClaimsPrincipal principal, string subject, string client, string type, ImmutableArray scopes, CancellationToken cancellationToken) - => await CreateAsync(principal, subject, client, type, scopes, cancellationToken); + /// + async ValueTask IOpenIddictAuthorizationManager.CreateAsync(ClaimsPrincipal principal, string subject, string client, string type, ImmutableArray scopes, CancellationToken cancellationToken) + => await CreateAsync(principal, subject, client, type, scopes, cancellationToken); - /// - async ValueTask IOpenIddictAuthorizationManager.CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) - => await CreateAsync(descriptor, cancellationToken); + /// + async ValueTask IOpenIddictAuthorizationManager.CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) + => await CreateAsync(descriptor, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.CreateAsync(object authorization, CancellationToken cancellationToken) - => CreateAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.CreateAsync(object authorization, CancellationToken cancellationToken) + => CreateAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.DeleteAsync(object authorization, CancellationToken cancellationToken) - => DeleteAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.DeleteAsync(object authorization, CancellationToken cancellationToken) + => DeleteAsync((TAuthorization) authorization, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, CancellationToken cancellationToken) - => FindAsync(subject, client, cancellationToken); + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, CancellationToken cancellationToken) + => FindAsync(subject, client, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, CancellationToken cancellationToken) - => FindAsync(subject, client, status, cancellationToken); + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, CancellationToken cancellationToken) + => FindAsync(subject, client, status, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) - => FindAsync(subject, client, status, type, cancellationToken); + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + => FindAsync(subject, client, status, type, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, string type, ImmutableArray scopes, CancellationToken cancellationToken) - => FindAsync(subject, client, status, type, scopes, cancellationToken); + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, string type, ImmutableArray scopes, CancellationToken cancellationToken) + => FindAsync(subject, client, status, type, scopes, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) - => FindByApplicationIdAsync(identifier, cancellationToken); + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) + => FindByApplicationIdAsync(identifier, cancellationToken); - /// - async ValueTask IOpenIddictAuthorizationManager.FindByIdAsync(string identifier, CancellationToken cancellationToken) - => await FindByIdAsync(identifier, cancellationToken); + /// + async ValueTask IOpenIddictAuthorizationManager.FindByIdAsync(string identifier, CancellationToken cancellationToken) + => await FindByIdAsync(identifier, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.FindBySubjectAsync(string subject, CancellationToken cancellationToken) - => FindBySubjectAsync(subject, cancellationToken); + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.FindBySubjectAsync(string subject, CancellationToken cancellationToken) + => FindBySubjectAsync(subject, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.GetApplicationIdAsync(object authorization, CancellationToken cancellationToken) - => GetApplicationIdAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.GetApplicationIdAsync(object authorization, CancellationToken cancellationToken) + => GetApplicationIdAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.GetAsync(Func, IQueryable> query, CancellationToken cancellationToken) where TResult : default - => GetAsync(query, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.GetAsync(Func, IQueryable> query, CancellationToken cancellationToken) where TResult : default + => GetAsync(query, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) where TResult : default - => GetAsync(query, state, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) where TResult : default + => GetAsync(query, state, cancellationToken); - ValueTask IOpenIddictAuthorizationManager.GetCreationDateAsync(object authorization, CancellationToken cancellationToken) - => GetCreationDateAsync((TAuthorization) authorization, cancellationToken); + ValueTask IOpenIddictAuthorizationManager.GetCreationDateAsync(object authorization, CancellationToken cancellationToken) + => GetCreationDateAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.GetIdAsync(object authorization, CancellationToken cancellationToken) - => GetIdAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.GetIdAsync(object authorization, CancellationToken cancellationToken) + => GetIdAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask> IOpenIddictAuthorizationManager.GetPropertiesAsync(object authorization, CancellationToken cancellationToken) - => GetPropertiesAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask> IOpenIddictAuthorizationManager.GetPropertiesAsync(object authorization, CancellationToken cancellationToken) + => GetPropertiesAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask> IOpenIddictAuthorizationManager.GetScopesAsync(object authorization, CancellationToken cancellationToken) - => GetScopesAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask> IOpenIddictAuthorizationManager.GetScopesAsync(object authorization, CancellationToken cancellationToken) + => GetScopesAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.GetStatusAsync(object authorization, CancellationToken cancellationToken) - => GetStatusAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.GetStatusAsync(object authorization, CancellationToken cancellationToken) + => GetStatusAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.GetSubjectAsync(object authorization, CancellationToken cancellationToken) - => GetSubjectAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.GetSubjectAsync(object authorization, CancellationToken cancellationToken) + => GetSubjectAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.GetTypeAsync(object authorization, CancellationToken cancellationToken) - => GetTypeAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.GetTypeAsync(object authorization, CancellationToken cancellationToken) + => GetTypeAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.HasScopesAsync(object authorization, ImmutableArray scopes, CancellationToken cancellationToken) - => HasScopesAsync((TAuthorization) authorization, scopes, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.HasScopesAsync(object authorization, ImmutableArray scopes, CancellationToken cancellationToken) + => HasScopesAsync((TAuthorization) authorization, scopes, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.HasStatusAsync(object authorization, string status, CancellationToken cancellationToken) - => HasStatusAsync((TAuthorization) authorization, status, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.HasStatusAsync(object authorization, string status, CancellationToken cancellationToken) + => HasStatusAsync((TAuthorization) authorization, status, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.HasTypeAsync(object authorization, string type, CancellationToken cancellationToken) - => HasTypeAsync((TAuthorization) authorization, type, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.HasTypeAsync(object authorization, string type, CancellationToken cancellationToken) + => HasTypeAsync((TAuthorization) authorization, type, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) - => ListAsync(count, offset, cancellationToken); + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) + => ListAsync(count, offset, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(Func, IQueryable> query, CancellationToken cancellationToken) - => ListAsync(query, cancellationToken); + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(Func, IQueryable> query, CancellationToken cancellationToken) + => ListAsync(query, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) - => ListAsync(query, state, cancellationToken); + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) + => ListAsync(query, state, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.PopulateAsync(OpenIddictAuthorizationDescriptor descriptor, object authorization, CancellationToken cancellationToken) - => PopulateAsync(descriptor, (TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.PopulateAsync(OpenIddictAuthorizationDescriptor descriptor, object authorization, CancellationToken cancellationToken) + => PopulateAsync(descriptor, (TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.PopulateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) - => PopulateAsync((TAuthorization) authorization, descriptor, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.PopulateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) + => PopulateAsync((TAuthorization) authorization, descriptor, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) - => PruneAsync(threshold, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + => PruneAsync(threshold, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.TryRevokeAsync(object authorization, CancellationToken cancellationToken) - => TryRevokeAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.TryRevokeAsync(object authorization, CancellationToken cancellationToken) + => TryRevokeAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.UpdateAsync(object authorization, CancellationToken cancellationToken) - => UpdateAsync((TAuthorization) authorization, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.UpdateAsync(object authorization, CancellationToken cancellationToken) + => UpdateAsync((TAuthorization) authorization, cancellationToken); - /// - ValueTask IOpenIddictAuthorizationManager.UpdateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) - => UpdateAsync((TAuthorization) authorization, descriptor, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.UpdateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) + => UpdateAsync((TAuthorization) authorization, descriptor, cancellationToken); - /// - IAsyncEnumerable IOpenIddictAuthorizationManager.ValidateAsync(object authorization, CancellationToken cancellationToken) - => ValidateAsync((TAuthorization) authorization, cancellationToken); - } -} \ No newline at end of file + /// + IAsyncEnumerable IOpenIddictAuthorizationManager.ValidateAsync(object authorization, CancellationToken cancellationToken) + => ValidateAsync((TAuthorization) authorization, cancellationToken); +} diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs index be30330e..fecf4dd8 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs @@ -21,1074 +21,1073 @@ using OpenIddict.Abstractions; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Provides methods allowing to manage the scopes stored in the store. +/// +/// +/// Applications that do not want to depend on a specific entity type can use the non-generic +/// instead, for which the actual entity type +/// is resolved at runtime based on the default entity type registered in the core options. +/// +/// The type of the Scope entity. +public class OpenIddictScopeManager : IOpenIddictScopeManager where TScope : class { + public OpenIddictScopeManager( + IOpenIddictScopeCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictScopeStoreResolver resolver) + { + Cache = cache; + Logger = logger; + Options = options; + Store = resolver.Get(); + } + + /// + /// Gets the cache associated with the current manager. + /// + protected IOpenIddictScopeCache Cache { get; } + + /// + /// Gets the logger associated with the current manager. + /// + protected ILogger Logger { get; } + + /// + /// Gets the options associated with the current manager. + /// + protected IOptionsMonitor Options { get; } + /// - /// Provides methods allowing to manage the scopes stored in the store. + /// Gets the store associated with the current manager. /// - /// - /// Applications that do not want to depend on a specific entity type can use the non-generic - /// instead, for which the actual entity type - /// is resolved at runtime based on the default entity type registered in the core options. - /// - /// The type of the Scope entity. - public class OpenIddictScopeManager : IOpenIddictScopeManager where TScope : class + protected IOpenIddictScopeStore Store { get; } + + /// + /// Determines the number of scopes that exist in the database. + /// + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of scopes in the database. + /// + public virtual ValueTask CountAsync(CancellationToken cancellationToken = default) + => Store.CountAsync(cancellationToken); + + /// + /// Determines the number of scopes that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of scopes that match the specified query. + /// + public virtual ValueTask CountAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) { - public OpenIddictScopeManager( - IOpenIddictScopeCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictScopeStoreResolver resolver) + if (query is null) { - Cache = cache; - Logger = logger; - Options = options; - Store = resolver.Get(); + throw new ArgumentNullException(nameof(query)); } - /// - /// Gets the cache associated with the current manager. - /// - protected IOpenIddictScopeCache Cache { get; } - - /// - /// Gets the logger associated with the current manager. - /// - protected ILogger Logger { get; } - - /// - /// Gets the options associated with the current manager. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the store associated with the current manager. - /// - protected IOpenIddictScopeStore Store { get; } - - /// - /// Determines the number of scopes that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of scopes in the database. - /// - public virtual ValueTask CountAsync(CancellationToken cancellationToken = default) - => Store.CountAsync(cancellationToken); - - /// - /// Determines the number of scopes that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of scopes that match the specified query. - /// - public virtual ValueTask CountAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + return Store.CountAsync(query, cancellationToken); + } - return Store.CountAsync(query, cancellationToken); + /// + /// Creates a new scope. + /// + /// The scope to create. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Creates a new scope. - /// - /// The scope to create. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken = default) + var results = await GetValidationResultsAsync(scope, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - var results = await GetValidationResultsAsync(scope, cancellationToken); - if (results.Any(result => result != ValidationResult.Success)) - { - var builder = new StringBuilder(); - builder.AppendLine(SR.GetResourceString(SR.ID0222)); - builder.AppendLine(); - - foreach (var result in results) - { - builder.AppendLine(result.ErrorMessage); - } - - throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); - } - - await Store.CreateAsync(scope, cancellationToken); + var builder = new StringBuilder(); + builder.AppendLine(SR.GetResourceString(SR.ID0222)); + builder.AppendLine(); - if (!Options.CurrentValue.DisableEntityCaching) + foreach (var result in results) { - await Cache.AddAsync(scope, cancellationToken); + builder.AppendLine(result.ErrorMessage); } - async Task> GetValidationResultsAsync( - TScope scope, CancellationToken cancellationToken) - { - var builder = ImmutableArray.CreateBuilder(); + throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); + } - await foreach (var result in ValidateAsync(scope, cancellationToken)) - { - builder.Add(result); - } + await Store.CreateAsync(scope, cancellationToken); - return builder.ToImmutable(); - } + if (!Options.CurrentValue.DisableEntityCaching) + { + await Cache.AddAsync(scope, cancellationToken); } - /// - /// Creates a new scope based on the specified descriptor. - /// - /// The scope descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the scope. - /// - public virtual async ValueTask CreateAsync( - OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default) + async Task> GetValidationResultsAsync( + TScope scope, CancellationToken cancellationToken) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + var builder = ImmutableArray.CreateBuilder(); - var scope = await Store.InstantiateAsync(cancellationToken); - if (scope is null) + await foreach (var result in ValidateAsync(scope, cancellationToken)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0223)); + builder.Add(result); } - await PopulateAsync(scope, descriptor, cancellationToken); - await CreateAsync(scope, cancellationToken); + return builder.ToImmutable(); + } + } - return scope; + /// + /// Creates a new scope based on the specified descriptor. + /// + /// The scope descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result returns the scope. + /// + public virtual async ValueTask CreateAsync( + OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (descriptor is null) + { + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Removes an existing scope. - /// - /// The scope to delete. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken = default) + var scope = await Store.InstantiateAsync(cancellationToken); + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0223)); + } - if (!Options.CurrentValue.DisableEntityCaching) - { - await Cache.RemoveAsync(scope, cancellationToken); - } + await PopulateAsync(scope, descriptor, cancellationToken); + await CreateAsync(scope, cancellationToken); + + return scope; + } - await Store.DeleteAsync(scope, cancellationToken); + /// + /// Removes an existing scope. + /// + /// The scope to delete. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Retrieves a scope using its unique identifier. - /// - /// The unique identifier associated with the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scope corresponding to the identifier. - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default) + if (!Options.CurrentValue.DisableEntityCaching) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + await Cache.RemoveAsync(scope, cancellationToken); + } - var scope = Options.CurrentValue.DisableEntityCaching ? - await Store.FindByIdAsync(identifier, cancellationToken) : - await Cache.FindByIdAsync(identifier, cancellationToken); + await Store.DeleteAsync(scope, cancellationToken); + } - if (scope is null) - { - return null; - } + /// + /// Retrieves a scope using its unique identifier. + /// + /// The unique identifier associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the identifier. + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - if (!Options.CurrentValue.DisableAdditionalFiltering && - !string.Equals(await Store.GetIdAsync(scope, cancellationToken), identifier, StringComparison.Ordinal)) - { - return null; - } + var scope = Options.CurrentValue.DisableEntityCaching ? + await Store.FindByIdAsync(identifier, cancellationToken) : + await Cache.FindByIdAsync(identifier, cancellationToken); - return scope; + if (scope is null) + { + return null; } - /// - /// Retrieves a scope using its name. - /// - /// The name associated with the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the scope corresponding to the specified name. - /// - public virtual async ValueTask FindByNameAsync(string name, CancellationToken cancellationToken = default) + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + if (!Options.CurrentValue.DisableAdditionalFiltering && + !string.Equals(await Store.GetIdAsync(scope, cancellationToken), identifier, StringComparison.Ordinal)) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); - } + return null; + } - var scope = Options.CurrentValue.DisableEntityCaching ? - await Store.FindByNameAsync(name, cancellationToken) : - await Cache.FindByNameAsync(name, cancellationToken); + return scope; + } - if (scope is null) - { - return null; - } + /// + /// Retrieves a scope using its name. + /// + /// The name associated with the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the scope corresponding to the specified name. + /// + public virtual async ValueTask FindByNameAsync(string name, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + var scope = Options.CurrentValue.DisableEntityCaching ? + await Store.FindByNameAsync(name, cancellationToken) : + await Cache.FindByNameAsync(name, cancellationToken); - if (!Options.CurrentValue.DisableAdditionalFiltering && - !string.Equals(await Store.GetNameAsync(scope, cancellationToken), name, StringComparison.Ordinal)) - { - return null; - } + if (scope is null) + { + return null; + } - return scope; + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + + if (!Options.CurrentValue.DisableAdditionalFiltering && + !string.Equals(await Store.GetNameAsync(scope, cancellationToken), name, StringComparison.Ordinal)) + { + return null; } - /// - /// Retrieves a list of scopes using their name. - /// - /// The names associated with the scopes. - /// The that can be used to abort the operation. - /// The scopes corresponding to the specified names. - public virtual IAsyncEnumerable FindByNamesAsync( - ImmutableArray names, CancellationToken cancellationToken = default) + return scope; + } + + /// + /// Retrieves a list of scopes using their name. + /// + /// The names associated with the scopes. + /// The that can be used to abort the operation. + /// The scopes corresponding to the specified names. + public virtual IAsyncEnumerable FindByNamesAsync( + ImmutableArray names, CancellationToken cancellationToken = default) + { + if (names.Any(name => string.IsNullOrEmpty(name))) { - if (names.Any(name => string.IsNullOrEmpty(name))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); + } - var scopes = Options.CurrentValue.DisableEntityCaching ? - Store.FindByNamesAsync(names, cancellationToken) : - Cache.FindByNamesAsync(names, cancellationToken); + var scopes = Options.CurrentValue.DisableEntityCaching ? + Store.FindByNamesAsync(names, cancellationToken) : + Cache.FindByNamesAsync(names, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return scopes; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return scopes; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var scope in scopes) { - await foreach (var scope in scopes) + if (names.Contains(await Store.GetNameAsync(scope, cancellationToken), StringComparer.Ordinal)) { - if (names.Contains(await Store.GetNameAsync(scope, cancellationToken), StringComparer.Ordinal)) - { - yield return scope; - } + yield return scope; } } } + } - /// - /// Retrieves all the scopes that contain the specified resource. - /// - /// The resource associated with the scopes. - /// The that can be used to abort the operation. - /// The scopes associated with the specified resource. - public virtual IAsyncEnumerable FindByResourceAsync( - string resource, CancellationToken cancellationToken = default) + /// + /// Retrieves all the scopes that contain the specified resource. + /// + /// The resource associated with the scopes. + /// The that can be used to abort the operation. + /// The scopes associated with the specified resource. + public virtual IAsyncEnumerable FindByResourceAsync( + string resource, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(resource)) { - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); + } - var scopes = Options.CurrentValue.DisableEntityCaching ? - Store.FindByResourceAsync(resource, cancellationToken) : - Cache.FindByResourceAsync(resource, cancellationToken); + var scopes = Options.CurrentValue.DisableEntityCaching ? + Store.FindByResourceAsync(resource, cancellationToken) : + Cache.FindByResourceAsync(resource, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return scopes; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return scopes; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var scope in scopes) { - await foreach (var scope in scopes) + var resources = await Store.GetResourcesAsync(scope, cancellationToken); + if (resources.Contains(resource, StringComparer.Ordinal)) { - var resources = await Store.GetResourcesAsync(scope, cancellationToken); - if (resources.Contains(resource, StringComparer.Ordinal)) - { - yield return scope; - } + yield return scope; } } } + } - /// - /// Executes the specified query and returns the first element. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - public virtual ValueTask GetAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) + /// + /// Executes the specified query and returns the first element. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + public virtual ValueTask GetAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return GetAsync(static (scopes, query) => query(scopes), query, cancellationToken); + throw new ArgumentNullException(nameof(query)); } - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - public virtual ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return Store.GetAsync(query, state, cancellationToken); - } + return GetAsync(static (scopes, query) => query(scopes), query, cancellationToken); + } - /// - /// Retrieves the description associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the description associated with the specified scope. - /// - public virtual ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken = default) + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + public virtual ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default) + { + if (query is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - return Store.GetDescriptionAsync(scope, cancellationToken); + throw new ArgumentNullException(nameof(query)); } - /// - /// Retrieves the localized descriptions associated with an scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the localized descriptions associated with the scope. - /// - public virtual async ValueTask> GetDescriptionsAsync( - TScope scope, CancellationToken cancellationToken = default) + return Store.GetAsync(query, state, cancellationToken); + } + + /// + /// Retrieves the description associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the description associated with the specified scope. + /// + public virtual ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - var descriptions = await Store.GetDescriptionsAsync(scope, cancellationToken); - if (descriptions is null || descriptions.Count == 0) - { - return ImmutableDictionary.Create(); - } + return Store.GetDescriptionAsync(scope, cancellationToken); + } - return descriptions; + /// + /// Retrieves the localized descriptions associated with an scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the localized descriptions associated with the scope. + /// + public virtual async ValueTask> GetDescriptionsAsync( + TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Retrieves the display name associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the display name associated with the scope. - /// - public virtual ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken = default) + var descriptions = await Store.GetDescriptionsAsync(scope, cancellationToken); + if (descriptions is null || descriptions.Count == 0) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - return Store.GetDisplayNameAsync(scope, cancellationToken); + return ImmutableDictionary.Create(); } - /// - /// Retrieves the localized display names associated with an scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the localized display names associated with the scope. - /// - public virtual async ValueTask> GetDisplayNamesAsync( - TScope scope, CancellationToken cancellationToken = default) + return descriptions; + } + + /// + /// Retrieves the display name associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the display name associated with the scope. + /// + public virtual ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - var names = await Store.GetDisplayNamesAsync(scope, cancellationToken); - if (names is null || names.Count == 0) - { - return ImmutableDictionary.Create(); - } + return Store.GetDisplayNameAsync(scope, cancellationToken); + } - return names; + /// + /// Retrieves the localized display names associated with an scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the localized display names associated with the scope. + /// + public virtual async ValueTask> GetDisplayNamesAsync( + TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Retrieves the unique identifier associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the scope. - /// - public virtual ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken = default) + var names = await Store.GetDisplayNamesAsync(scope, cancellationToken); + if (names is null || names.Count == 0) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - return Store.GetIdAsync(scope, cancellationToken); + return ImmutableDictionary.Create(); } - /// - /// Retrieves the localized display name associated with an scope - /// and corresponding to the current UI culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching display name associated with the scope. - /// - public virtual ValueTask GetLocalizedDisplayNameAsync(TScope scope, CancellationToken cancellationToken = default) - => GetLocalizedDisplayNameAsync(scope, CultureInfo.CurrentUICulture, cancellationToken); - - /// - /// Retrieves the localized display name associated with an scope - /// and corresponding to the specified culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The scope. - /// The culture (typically ). - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching display name associated with the scope. - /// - public virtual async ValueTask GetLocalizedDisplayNameAsync( - TScope scope, CultureInfo culture, CancellationToken cancellationToken = default) - { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + return names; + } - if (culture is null) - { - throw new ArgumentNullException(nameof(culture)); - } + /// + /// Retrieves the unique identifier associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the scope. + /// + public virtual ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - var names = await Store.GetDisplayNamesAsync(scope, cancellationToken); - if (names is null || names.IsEmpty) - { - return await Store.GetDisplayNameAsync(scope, cancellationToken); - } + return Store.GetIdAsync(scope, cancellationToken); + } - do - { - if (names.TryGetValue(culture, out var name)) - { - return name; - } + /// + /// Retrieves the localized display name associated with an scope + /// and corresponding to the current UI culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching display name associated with the scope. + /// + public virtual ValueTask GetLocalizedDisplayNameAsync(TScope scope, CancellationToken cancellationToken = default) + => GetLocalizedDisplayNameAsync(scope, CultureInfo.CurrentUICulture, cancellationToken); - culture = culture.Parent; - } + /// + /// Retrieves the localized display name associated with an scope + /// and corresponding to the specified culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The scope. + /// The culture (typically ). + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching display name associated with the scope. + /// + public virtual async ValueTask GetLocalizedDisplayNameAsync( + TScope scope, CultureInfo culture, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - while (culture != CultureInfo.InvariantCulture); + if (culture is null) + { + throw new ArgumentNullException(nameof(culture)); + } + var names = await Store.GetDisplayNamesAsync(scope, cancellationToken); + if (names is null || names.IsEmpty) + { return await Store.GetDisplayNameAsync(scope, cancellationToken); } - /// - /// Retrieves the localized description associated with an scope - /// and corresponding to the current UI culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching localized description associated with the scope. - /// - public virtual ValueTask GetLocalizedDescriptionAsync(TScope scope, CancellationToken cancellationToken = default) - => GetLocalizedDescriptionAsync(scope, CultureInfo.CurrentUICulture, cancellationToken); - - /// - /// Retrieves the localized description associated with an scope - /// and corresponding to the specified culture or one of its parents. - /// If no matching value can be found, the non-localized value is returned. - /// - /// The scope. - /// The culture (typically ). - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the matching localized description associated with the scope. - /// - public virtual async ValueTask GetLocalizedDescriptionAsync( - TScope scope, CultureInfo culture, CancellationToken cancellationToken = default) + do { - if (scope is null) + if (names.TryGetValue(culture, out var name)) { - throw new ArgumentNullException(nameof(scope)); + return name; } - if (culture is null) - { - throw new ArgumentNullException(nameof(culture)); - } + culture = culture.Parent; + } - var descriptions = await Store.GetDescriptionsAsync(scope, cancellationToken); - if (descriptions is null || descriptions.IsEmpty) - { - return await Store.GetDescriptionAsync(scope, cancellationToken); - } + while (culture != CultureInfo.InvariantCulture); - do - { - if (descriptions.TryGetValue(culture, out var description)) - { - return description; - } - - culture = culture.Parent; - } + return await Store.GetDisplayNameAsync(scope, cancellationToken); + } - while (culture != CultureInfo.InvariantCulture); + /// + /// Retrieves the localized description associated with an scope + /// and corresponding to the current UI culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching localized description associated with the scope. + /// + public virtual ValueTask GetLocalizedDescriptionAsync(TScope scope, CancellationToken cancellationToken = default) + => GetLocalizedDescriptionAsync(scope, CultureInfo.CurrentUICulture, cancellationToken); - return await Store.GetDescriptionAsync(scope, cancellationToken); + /// + /// Retrieves the localized description associated with an scope + /// and corresponding to the specified culture or one of its parents. + /// If no matching value can be found, the non-localized value is returned. + /// + /// The scope. + /// The culture (typically ). + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the matching localized description associated with the scope. + /// + public virtual async ValueTask GetLocalizedDescriptionAsync( + TScope scope, CultureInfo culture, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Retrieves the name associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the name associated with the specified scope. - /// - public virtual ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken = default) + if (culture is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(culture)); + } - return Store.GetNameAsync(scope, cancellationToken); + var descriptions = await Store.GetDescriptionsAsync(scope, cancellationToken); + if (descriptions is null || descriptions.IsEmpty) + { + return await Store.GetDescriptionAsync(scope, cancellationToken); } - /// - /// Retrieves the additional properties associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the scope. - /// - public virtual ValueTask> GetPropertiesAsync( - TScope scope, CancellationToken cancellationToken = default) + do { - if (scope is null) + if (descriptions.TryGetValue(culture, out var description)) { - throw new ArgumentNullException(nameof(scope)); + return description; } - return Store.GetPropertiesAsync(scope, cancellationToken); + culture = culture.Parent; } - /// - /// Retrieves the resources associated with a scope. - /// - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the resources associated with the scope. - /// - public virtual ValueTask> GetResourcesAsync( - TScope scope, CancellationToken cancellationToken = default) - { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + while (culture != CultureInfo.InvariantCulture); + + return await Store.GetDescriptionAsync(scope, cancellationToken); + } - return Store.GetResourcesAsync(scope, cancellationToken); + /// + /// Retrieves the name associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the name associated with the specified scope. + /// + public virtual ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - int? count = null, int? offset = null, CancellationToken cancellationToken = default) - => Store.ListAsync(count, offset, cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) + return Store.GetNameAsync(scope, cancellationToken); + } + + /// + /// Retrieves the additional properties associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the scope. + /// + public virtual ValueTask> GetPropertiesAsync( + TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(scope)); + } + + return Store.GetPropertiesAsync(scope, cancellationToken); + } - return ListAsync(static (scopes, query) => query(scopes), query, cancellationToken); + /// + /// Retrieves the resources associated with a scope. + /// + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the resources associated with the scope. + /// + public virtual ValueTask> GetResourcesAsync( + TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default) + return Store.GetResourcesAsync(scope, cancellationToken); + } + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + int? count = null, int? offset = null, CancellationToken cancellationToken = default) + => Store.ListAsync(count, offset, cancellationToken); + + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(query)); + } + + return ListAsync(static (scopes, query) => query(scopes), query, cancellationToken); + } - return Store.ListAsync(query, state, cancellationToken); + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - /// Lists all the resources associated with the specified scopes. - /// - /// The scopes. - /// The that can be used to abort the operation. - /// All the resources associated with the specified scopes. - public virtual async IAsyncEnumerable ListResourcesAsync( - ImmutableArray scopes, [EnumeratorCancellation] CancellationToken cancellationToken = default) + return Store.ListAsync(query, state, cancellationToken); + } + + /// + /// Lists all the resources associated with the specified scopes. + /// + /// The scopes. + /// The that can be used to abort the operation. + /// All the resources associated with the specified scopes. + public virtual async IAsyncEnumerable ListResourcesAsync( + ImmutableArray scopes, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var resources = new HashSet(StringComparer.Ordinal); + + await foreach (var scope in FindByNamesAsync(scopes, cancellationToken)) { - var resources = new HashSet(StringComparer.Ordinal); + resources.UnionWith(await GetResourcesAsync(scope, cancellationToken)); + } - await foreach (var scope in FindByNamesAsync(scopes, cancellationToken)) - { - resources.UnionWith(await GetResourcesAsync(scope, cancellationToken)); - } + foreach (var resource in resources) + { + yield return resource; + } + } - foreach (var resource in resources) - { - yield return resource; - } + /// + /// Populates the scope using the specified descriptor. + /// + /// The scope. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask PopulateAsync(TScope scope, + OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Populates the scope using the specified descriptor. - /// - /// The scope. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask PopulateAsync(TScope scope, - OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default) + if (descriptor is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + await Store.SetDescriptionAsync(scope, descriptor.Description, cancellationToken); + await Store.SetDescriptionsAsync(scope, descriptor.Descriptions.ToImmutableDictionary(), cancellationToken); + await Store.SetDisplayNameAsync(scope, descriptor.DisplayName, cancellationToken); + await Store.SetDisplayNamesAsync(scope, descriptor.DisplayNames.ToImmutableDictionary(), cancellationToken); + await Store.SetNameAsync(scope, descriptor.Name, cancellationToken); + await Store.SetPropertiesAsync(scope, descriptor.Properties.ToImmutableDictionary(), cancellationToken); + await Store.SetResourcesAsync(scope, descriptor.Resources.ToImmutableArray(), cancellationToken); + } - await Store.SetDescriptionAsync(scope, descriptor.Description, cancellationToken); - await Store.SetDescriptionsAsync(scope, descriptor.Descriptions.ToImmutableDictionary(), cancellationToken); - await Store.SetDisplayNameAsync(scope, descriptor.DisplayName, cancellationToken); - await Store.SetDisplayNamesAsync(scope, descriptor.DisplayNames.ToImmutableDictionary(), cancellationToken); - await Store.SetNameAsync(scope, descriptor.Name, cancellationToken); - await Store.SetPropertiesAsync(scope, descriptor.Properties.ToImmutableDictionary(), cancellationToken); - await Store.SetResourcesAsync(scope, descriptor.Resources.ToImmutableArray(), cancellationToken); + /// + /// Populates the specified descriptor using the properties exposed by the scope. + /// + /// The descriptor. + /// The scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask PopulateAsync( + OpenIddictScopeDescriptor descriptor, + TScope scope, CancellationToken cancellationToken = default) + { + if (descriptor is null) + { + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Populates the specified descriptor using the properties exposed by the scope. - /// - /// The descriptor. - /// The scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask PopulateAsync( - OpenIddictScopeDescriptor descriptor, - TScope scope, CancellationToken cancellationToken = default) + if (scope is null) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + throw new ArgumentNullException(nameof(scope)); + } - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + descriptor.Description = await Store.GetDescriptionAsync(scope, cancellationToken); + descriptor.DisplayName = await Store.GetDisplayNameAsync(scope, cancellationToken); + descriptor.Name = await Store.GetNameAsync(scope, cancellationToken); + descriptor.Resources.Clear(); + descriptor.Resources.UnionWith(await Store.GetResourcesAsync(scope, cancellationToken)); - descriptor.Description = await Store.GetDescriptionAsync(scope, cancellationToken); - descriptor.DisplayName = await Store.GetDisplayNameAsync(scope, cancellationToken); - descriptor.Name = await Store.GetNameAsync(scope, cancellationToken); - descriptor.Resources.Clear(); - descriptor.Resources.UnionWith(await Store.GetResourcesAsync(scope, cancellationToken)); + descriptor.DisplayNames.Clear(); + foreach (var pair in await Store.GetDisplayNamesAsync(scope, cancellationToken)) + { + descriptor.DisplayNames.Add(pair.Key, pair.Value); + } - descriptor.DisplayNames.Clear(); - foreach (var pair in await Store.GetDisplayNamesAsync(scope, cancellationToken)) - { - descriptor.DisplayNames.Add(pair.Key, pair.Value); - } + descriptor.Descriptions.Clear(); + foreach (var pair in await Store.GetDescriptionsAsync(scope, cancellationToken)) + { + descriptor.Descriptions.Add(pair.Key, pair.Value); + } - descriptor.Descriptions.Clear(); - foreach (var pair in await Store.GetDescriptionsAsync(scope, cancellationToken)) - { - descriptor.Descriptions.Add(pair.Key, pair.Value); - } + descriptor.Properties.Clear(); + foreach (var pair in await Store.GetPropertiesAsync(scope, cancellationToken)) + { + descriptor.Properties.Add(pair.Key, pair.Value); + } + } - descriptor.Properties.Clear(); - foreach (var pair in await Store.GetPropertiesAsync(scope, cancellationToken)) - { - descriptor.Properties.Add(pair.Key, pair.Value); - } + /// + /// Updates an existing scope. + /// + /// The scope to update. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Updates an existing scope. - /// - /// The scope to update. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken = default) + var results = await GetValidationResultsAsync(scope, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) { - if (scope is null) + var builder = new StringBuilder(); + builder.AppendLine(SR.GetResourceString(SR.ID0224)); + builder.AppendLine(); + + foreach (var result in results) { - throw new ArgumentNullException(nameof(scope)); + builder.AppendLine(result.ErrorMessage); } - var results = await GetValidationResultsAsync(scope, cancellationToken); - if (results.Any(result => result != ValidationResult.Success)) - { - var builder = new StringBuilder(); - builder.AppendLine(SR.GetResourceString(SR.ID0224)); - builder.AppendLine(); + throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); + } - foreach (var result in results) - { - builder.AppendLine(result.ErrorMessage); - } + await Store.UpdateAsync(scope, cancellationToken); - throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); - } + if (!Options.CurrentValue.DisableEntityCaching) + { + await Cache.RemoveAsync(scope, cancellationToken); + await Cache.AddAsync(scope, cancellationToken); + } - await Store.UpdateAsync(scope, cancellationToken); + async Task> GetValidationResultsAsync( + TScope scope, CancellationToken cancellationToken) + { + var builder = ImmutableArray.CreateBuilder(); - if (!Options.CurrentValue.DisableEntityCaching) + await foreach (var result in ValidateAsync(scope, cancellationToken)) { - await Cache.RemoveAsync(scope, cancellationToken); - await Cache.AddAsync(scope, cancellationToken); + builder.Add(result); } - async Task> GetValidationResultsAsync( - TScope scope, CancellationToken cancellationToken) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var result in ValidateAsync(scope, cancellationToken)) - { - builder.Add(result); - } + return builder.ToImmutable(); + } + } - return builder.ToImmutable(); - } + /// + /// Updates an existing scope. + /// + /// The scope to update. + /// The descriptor used to update the scope. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask UpdateAsync(TScope scope, + OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Updates an existing scope. - /// - /// The scope to update. - /// The descriptor used to update the scope. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask UpdateAsync(TScope scope, - OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default) + if (descriptor is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + await PopulateAsync(scope, descriptor, cancellationToken); + await UpdateAsync(scope, cancellationToken); + } - await PopulateAsync(scope, descriptor, cancellationToken); - await UpdateAsync(scope, cancellationToken); + /// + /// Validates the scope to ensure it's in a consistent state. + /// + /// The scope. + /// The that can be used to abort the operation. + /// The validation error encountered when validating the scope. + public virtual async IAsyncEnumerable ValidateAsync( + TScope scope, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - /// Validates the scope to ensure it's in a consistent state. - /// - /// The scope. - /// The that can be used to abort the operation. - /// The validation error encountered when validating the scope. - public virtual async IAsyncEnumerable ValidateAsync( - TScope scope, [EnumeratorCancellation] CancellationToken cancellationToken = default) + // Ensure the name is not null or empty, does not contain a + // space and is not already used for a different scope entity. + var name = await Store.GetNameAsync(scope, cancellationToken); + if (string.IsNullOrEmpty(name)) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - // Ensure the name is not null or empty, does not contain a - // space and is not already used for a different scope entity. - var name = await Store.GetNameAsync(scope, cancellationToken); - if (string.IsNullOrEmpty(name)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2044)); - } + yield return new ValidationResult(SR.GetResourceString(SR.ID2044)); + } - else if (name!.Contains(Separators.Space[0])) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2045)); - } + else if (name!.Contains(Separators.Space[0])) + { + yield return new ValidationResult(SR.GetResourceString(SR.ID2045)); + } - else + else + { + // Note: depending on the database/table/query collation used by the store, a scope + // whose name doesn't exactly match the specified value may be returned (e.g because + // the casing is different). To avoid issues when the scope name is part of an index + // using the same collation, an error is added even if the two names don't exactly match. + var other = await Store.FindByNameAsync(name, cancellationToken); + if (other is not null && !string.Equals( + await Store.GetIdAsync(other, cancellationToken), + await Store.GetIdAsync(scope, cancellationToken), StringComparison.Ordinal)) { - // Note: depending on the database/table/query collation used by the store, a scope - // whose name doesn't exactly match the specified value may be returned (e.g because - // the casing is different). To avoid issues when the scope name is part of an index - // using the same collation, an error is added even if the two names don't exactly match. - var other = await Store.FindByNameAsync(name, cancellationToken); - if (other is not null && !string.Equals( - await Store.GetIdAsync(other, cancellationToken), - await Store.GetIdAsync(scope, cancellationToken), StringComparison.Ordinal)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2060)); - } + yield return new ValidationResult(SR.GetResourceString(SR.ID2060)); } } + } - /// - ValueTask IOpenIddictScopeManager.CountAsync(CancellationToken cancellationToken) - => CountAsync(cancellationToken); + /// + ValueTask IOpenIddictScopeManager.CountAsync(CancellationToken cancellationToken) + => CountAsync(cancellationToken); - /// - ValueTask IOpenIddictScopeManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - => CountAsync(query, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) + => CountAsync(query, cancellationToken); - /// - async ValueTask IOpenIddictScopeManager.CreateAsync(OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) - => await CreateAsync(descriptor, cancellationToken); + /// + async ValueTask IOpenIddictScopeManager.CreateAsync(OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) + => await CreateAsync(descriptor, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.CreateAsync(object scope, CancellationToken cancellationToken) - => CreateAsync((TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.CreateAsync(object scope, CancellationToken cancellationToken) + => CreateAsync((TScope) scope, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.DeleteAsync(object scope, CancellationToken cancellationToken) - => DeleteAsync((TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.DeleteAsync(object scope, CancellationToken cancellationToken) + => DeleteAsync((TScope) scope, cancellationToken); - /// - async ValueTask IOpenIddictScopeManager.FindByIdAsync(string identifier, CancellationToken cancellationToken) - => await FindByIdAsync(identifier, cancellationToken); + /// + async ValueTask IOpenIddictScopeManager.FindByIdAsync(string identifier, CancellationToken cancellationToken) + => await FindByIdAsync(identifier, cancellationToken); - /// - async ValueTask IOpenIddictScopeManager.FindByNameAsync(string name, CancellationToken cancellationToken) - => await FindByNameAsync(name, cancellationToken); + /// + async ValueTask IOpenIddictScopeManager.FindByNameAsync(string name, CancellationToken cancellationToken) + => await FindByNameAsync(name, cancellationToken); - /// - IAsyncEnumerable IOpenIddictScopeManager.FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken) - => FindByNamesAsync(names, cancellationToken); + /// + IAsyncEnumerable IOpenIddictScopeManager.FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken) + => FindByNamesAsync(names, cancellationToken); - /// - IAsyncEnumerable IOpenIddictScopeManager.FindByResourceAsync(string resource, CancellationToken cancellationToken) - => FindByResourceAsync(resource, cancellationToken); + /// + IAsyncEnumerable IOpenIddictScopeManager.FindByResourceAsync(string resource, CancellationToken cancellationToken) + => FindByResourceAsync(resource, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetAsync(Func, IQueryable> query, CancellationToken cancellationToken) where TResult : default - => GetAsync(query, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetAsync(Func, IQueryable> query, CancellationToken cancellationToken) where TResult : default + => GetAsync(query, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) where TResult : default - => GetAsync(query, state, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) where TResult : default + => GetAsync(query, state, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetDescriptionAsync(object scope, CancellationToken cancellationToken) - => GetDescriptionAsync((TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetDescriptionAsync(object scope, CancellationToken cancellationToken) + => GetDescriptionAsync((TScope) scope, cancellationToken); - /// - ValueTask> IOpenIddictScopeManager.GetDescriptionsAsync(object scope, CancellationToken cancellationToken) - => GetDescriptionsAsync((TScope) scope, cancellationToken); + /// + ValueTask> IOpenIddictScopeManager.GetDescriptionsAsync(object scope, CancellationToken cancellationToken) + => GetDescriptionsAsync((TScope) scope, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetDisplayNameAsync(object scope, CancellationToken cancellationToken) - => GetDisplayNameAsync((TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetDisplayNameAsync(object scope, CancellationToken cancellationToken) + => GetDisplayNameAsync((TScope) scope, cancellationToken); - /// - ValueTask> IOpenIddictScopeManager.GetDisplayNamesAsync(object scope, CancellationToken cancellationToken) - => GetDisplayNamesAsync((TScope) scope, cancellationToken); + /// + ValueTask> IOpenIddictScopeManager.GetDisplayNamesAsync(object scope, CancellationToken cancellationToken) + => GetDisplayNamesAsync((TScope) scope, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetIdAsync(object scope, CancellationToken cancellationToken) - => GetIdAsync((TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetIdAsync(object scope, CancellationToken cancellationToken) + => GetIdAsync((TScope) scope, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetLocalizedDescriptionAsync(object scope, CancellationToken cancellationToken) - => GetLocalizedDescriptionAsync((TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetLocalizedDescriptionAsync(object scope, CancellationToken cancellationToken) + => GetLocalizedDescriptionAsync((TScope) scope, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetLocalizedDescriptionAsync(object scope, CultureInfo culture, CancellationToken cancellationToken) - => GetLocalizedDescriptionAsync((TScope) scope, culture, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetLocalizedDescriptionAsync(object scope, CultureInfo culture, CancellationToken cancellationToken) + => GetLocalizedDescriptionAsync((TScope) scope, culture, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetLocalizedDisplayNameAsync(object scope, CancellationToken cancellationToken) - => GetLocalizedDisplayNameAsync((TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetLocalizedDisplayNameAsync(object scope, CancellationToken cancellationToken) + => GetLocalizedDisplayNameAsync((TScope) scope, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetLocalizedDisplayNameAsync(object scope, CultureInfo culture, CancellationToken cancellationToken) - => GetLocalizedDisplayNameAsync((TScope) scope, culture, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetLocalizedDisplayNameAsync(object scope, CultureInfo culture, CancellationToken cancellationToken) + => GetLocalizedDisplayNameAsync((TScope) scope, culture, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.GetNameAsync(object scope, CancellationToken cancellationToken) - => GetNameAsync((TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.GetNameAsync(object scope, CancellationToken cancellationToken) + => GetNameAsync((TScope) scope, cancellationToken); - /// - ValueTask> IOpenIddictScopeManager.GetPropertiesAsync(object scope, CancellationToken cancellationToken) - => GetPropertiesAsync((TScope) scope, cancellationToken); + /// + ValueTask> IOpenIddictScopeManager.GetPropertiesAsync(object scope, CancellationToken cancellationToken) + => GetPropertiesAsync((TScope) scope, cancellationToken); - /// - ValueTask> IOpenIddictScopeManager.GetResourcesAsync(object scope, CancellationToken cancellationToken) - => GetResourcesAsync((TScope) scope, cancellationToken); + /// + ValueTask> IOpenIddictScopeManager.GetResourcesAsync(object scope, CancellationToken cancellationToken) + => GetResourcesAsync((TScope) scope, cancellationToken); - /// - IAsyncEnumerable IOpenIddictScopeManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) - => ListAsync(count, offset, cancellationToken); + /// + IAsyncEnumerable IOpenIddictScopeManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) + => ListAsync(count, offset, cancellationToken); - /// - IAsyncEnumerable IOpenIddictScopeManager.ListAsync(Func, IQueryable> query, CancellationToken cancellationToken) - => ListAsync(query, cancellationToken); + /// + IAsyncEnumerable IOpenIddictScopeManager.ListAsync(Func, IQueryable> query, CancellationToken cancellationToken) + => ListAsync(query, cancellationToken); - /// - IAsyncEnumerable IOpenIddictScopeManager.ListAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) - => ListAsync(query, state, cancellationToken); + /// + IAsyncEnumerable IOpenIddictScopeManager.ListAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) + => ListAsync(query, state, cancellationToken); - /// - IAsyncEnumerable IOpenIddictScopeManager.ListResourcesAsync(ImmutableArray scopes, CancellationToken cancellationToken) - => ListResourcesAsync(scopes, cancellationToken); + /// + IAsyncEnumerable IOpenIddictScopeManager.ListResourcesAsync(ImmutableArray scopes, CancellationToken cancellationToken) + => ListResourcesAsync(scopes, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.PopulateAsync(OpenIddictScopeDescriptor descriptor, object scope, CancellationToken cancellationToken) - => PopulateAsync(descriptor, (TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.PopulateAsync(OpenIddictScopeDescriptor descriptor, object scope, CancellationToken cancellationToken) + => PopulateAsync(descriptor, (TScope) scope, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.PopulateAsync(object scope, OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) - => PopulateAsync((TScope) scope, descriptor, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.PopulateAsync(object scope, OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) + => PopulateAsync((TScope) scope, descriptor, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.UpdateAsync(object scope, CancellationToken cancellationToken) - => UpdateAsync((TScope) scope, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.UpdateAsync(object scope, CancellationToken cancellationToken) + => UpdateAsync((TScope) scope, cancellationToken); - /// - ValueTask IOpenIddictScopeManager.UpdateAsync(object scope, OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) - => UpdateAsync((TScope) scope, descriptor, cancellationToken); + /// + ValueTask IOpenIddictScopeManager.UpdateAsync(object scope, OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken) + => UpdateAsync((TScope) scope, descriptor, cancellationToken); - /// - IAsyncEnumerable IOpenIddictScopeManager.ValidateAsync(object scope, CancellationToken cancellationToken) - => ValidateAsync((TScope) scope, cancellationToken); - } -} \ No newline at end of file + /// + IAsyncEnumerable IOpenIddictScopeManager.ValidateAsync(object scope, CancellationToken cancellationToken) + => ValidateAsync((TScope) scope, cancellationToken); +} diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index f1da7661..f70484b6 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -22,1495 +22,1494 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Abstractions.OpenIddictExceptions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Provides methods allowing to manage the tokens stored in the store. +/// +/// +/// Applications that do not want to depend on a specific entity type can use the non-generic +/// instead, for which the actual entity type +/// is resolved at runtime based on the default entity type registered in the core options. +/// +/// The type of the Token entity. +public class OpenIddictTokenManager : IOpenIddictTokenManager where TToken : class { + public OpenIddictTokenManager( + IOpenIddictTokenCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictTokenStoreResolver resolver) + { + Cache = cache; + Logger = logger; + Options = options; + Store = resolver.Get(); + } + + /// + /// Gets the cache associated with the current manager. + /// + protected IOpenIddictTokenCache Cache { get; } + + /// + /// Gets the logger associated with the current manager. + /// + protected ILogger Logger { get; } + + /// + /// Gets the options associated with the current manager. + /// + protected IOptionsMonitor Options { get; } + + /// + /// Gets the store associated with the current manager. + /// + protected IOpenIddictTokenStore Store { get; } + /// - /// Provides methods allowing to manage the tokens stored in the store. + /// Determines the number of tokens that exist in the database. /// - /// - /// Applications that do not want to depend on a specific entity type can use the non-generic - /// instead, for which the actual entity type - /// is resolved at runtime based on the default entity type registered in the core options. - /// - /// The type of the Token entity. - public class OpenIddictTokenManager : IOpenIddictTokenManager where TToken : class + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of tokens in the database. + /// + public virtual ValueTask CountAsync(CancellationToken cancellationToken = default) + => Store.CountAsync(cancellationToken); + + /// + /// Determines the number of tokens that match the specified query. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the number of tokens that match the specified query. + /// + public virtual ValueTask CountAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) { - public OpenIddictTokenManager( - IOpenIddictTokenCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictTokenStoreResolver resolver) - { - Cache = cache; - Logger = logger; - Options = options; - Store = resolver.Get(); - } - - /// - /// Gets the cache associated with the current manager. - /// - protected IOpenIddictTokenCache Cache { get; } - - /// - /// Gets the logger associated with the current manager. - /// - protected ILogger Logger { get; } - - /// - /// Gets the options associated with the current manager. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the store associated with the current manager. - /// - protected IOpenIddictTokenStore Store { get; } - - /// - /// Determines the number of tokens that exist in the database. - /// - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of tokens in the database. - /// - public virtual ValueTask CountAsync(CancellationToken cancellationToken = default) - => Store.CountAsync(cancellationToken); - - /// - /// Determines the number of tokens that match the specified query. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the number of tokens that match the specified query. - /// - public virtual ValueTask CountAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } - return Store.CountAsync(query, cancellationToken); + return Store.CountAsync(query, cancellationToken); + } + + /// + /// Creates a new token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask CreateAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - /// Creates a new token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask CreateAsync(TToken token, CancellationToken cancellationToken = default) + // If no status was explicitly specified, assume that the token is valid. + if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken))) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + await Store.SetStatusAsync(token, Statuses.Valid, cancellationToken); + } - // If no status was explicitly specified, assume that the token is valid. - if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken))) - { - await Store.SetStatusAsync(token, Statuses.Valid, cancellationToken); - } + // If a reference identifier was set, obfuscate it. + var identifier = await Store.GetReferenceIdAsync(token, cancellationToken); + if (!string.IsNullOrEmpty(identifier)) + { + identifier = await ObfuscateReferenceIdAsync(identifier, cancellationToken); + await Store.SetReferenceIdAsync(token, identifier, cancellationToken); + } + + var results = await GetValidationResultsAsync(token, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) + { + var builder = new StringBuilder(); + builder.AppendLine(SR.GetResourceString(SR.ID0225)); + builder.AppendLine(); - // If a reference identifier was set, obfuscate it. - var identifier = await Store.GetReferenceIdAsync(token, cancellationToken); - if (!string.IsNullOrEmpty(identifier)) + foreach (var result in results) { - identifier = await ObfuscateReferenceIdAsync(identifier, cancellationToken); - await Store.SetReferenceIdAsync(token, identifier, cancellationToken); + builder.AppendLine(result.ErrorMessage); } - var results = await GetValidationResultsAsync(token, cancellationToken); - if (results.Any(result => result != ValidationResult.Success)) - { - var builder = new StringBuilder(); - builder.AppendLine(SR.GetResourceString(SR.ID0225)); - builder.AppendLine(); + throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); + } - foreach (var result in results) - { - builder.AppendLine(result.ErrorMessage); - } + await Store.CreateAsync(token, cancellationToken); - throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); - } + if (!Options.CurrentValue.DisableEntityCaching) + { + await Cache.AddAsync(token, cancellationToken); + } - await Store.CreateAsync(token, cancellationToken); + async Task> GetValidationResultsAsync( + TToken token, CancellationToken cancellationToken) + { + var builder = ImmutableArray.CreateBuilder(); - if (!Options.CurrentValue.DisableEntityCaching) + await foreach (var result in ValidateAsync(token, cancellationToken)) { - await Cache.AddAsync(token, cancellationToken); + builder.Add(result); } - async Task> GetValidationResultsAsync( - TToken token, CancellationToken cancellationToken) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var result in ValidateAsync(token, cancellationToken)) - { - builder.Add(result); - } + return builder.ToImmutable(); + } + } - return builder.ToImmutable(); - } + /// + /// Creates a new token based on the specified descriptor. + /// + /// The token descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, whose result returns the token. + /// + public virtual async ValueTask CreateAsync( + OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (descriptor is null) + { + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Creates a new token based on the specified descriptor. - /// - /// The token descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, whose result returns the token. - /// - public virtual async ValueTask CreateAsync( - OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default) + var token = await Store.InstantiateAsync(cancellationToken); + if (token is null) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0226)); + } - var token = await Store.InstantiateAsync(cancellationToken); - if (token is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0226)); - } + await PopulateAsync(token, descriptor, cancellationToken); + await CreateAsync(token, cancellationToken); - await PopulateAsync(token, descriptor, cancellationToken); - await CreateAsync(token, cancellationToken); + return token; + } - return token; + /// + /// Removes an existing token. + /// + /// The token to delete. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - /// Removes an existing token. - /// - /// The token to delete. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken = default) + if (!Options.CurrentValue.DisableEntityCaching) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + await Cache.RemoveAsync(token, cancellationToken); + } - if (!Options.CurrentValue.DisableEntityCaching) - { - await Cache.RemoveAsync(token, cancellationToken); - } + await Store.DeleteAsync(token, cancellationToken); + } - await Store.DeleteAsync(token, cancellationToken); + /// + /// Retrieves the tokens corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The that can be used to abort the operation. + /// The tokens corresponding to the subject/client. + public virtual IAsyncEnumerable FindAsync(string subject, + string client, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - /// Retrieves the tokens corresponding to the specified - /// subject and associated with the application identifier. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The that can be used to abort the operation. - /// The tokens corresponding to the subject/client. - public virtual IAsyncEnumerable FindAsync(string subject, - string client, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - var tokens = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, cancellationToken) : - Cache.FindAsync(subject, client, cancellationToken); + var tokens = Options.CurrentValue.DisableEntityCaching ? + Store.FindAsync(subject, client, cancellationToken) : + Cache.FindAsync(subject, client, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return tokens; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return tokens; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var token in tokens) { - await foreach (var token in tokens) + if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return token; - } + yield return token; } } } + } - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The that can be used to abort the operation. - /// The tokens corresponding to the criteria. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken = default) + /// + /// Retrieves the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The that can be used to abort the operation. + /// The tokens corresponding to the criteria. + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - var tokens = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, status, cancellationToken) : - Cache.FindAsync(subject, client, status, cancellationToken); + var tokens = Options.CurrentValue.DisableEntityCaching ? + Store.FindAsync(subject, client, status, cancellationToken) : + Cache.FindAsync(subject, client, status, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return tokens; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return tokens; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var token in tokens) { - await foreach (var token in tokens) + if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return token; - } + yield return token; } } } + } - /// - /// Retrieves the tokens matching the specified parameters. - /// - /// The subject associated with the token. - /// The client associated with the token. - /// The token status. - /// The token type. - /// The that can be used to abort the operation. - /// Tokens corresponding to the criteria. - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + /// + /// Retrieves the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The token type. + /// The that can be used to abort the operation. + /// Tokens corresponding to the criteria. + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - var tokens = Options.CurrentValue.DisableEntityCaching ? - Store.FindAsync(subject, client, status, type, cancellationToken) : - Cache.FindAsync(subject, client, status, type, cancellationToken); + var tokens = Options.CurrentValue.DisableEntityCaching ? + Store.FindAsync(subject, client, status, type, cancellationToken) : + Cache.FindAsync(subject, client, status, type, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return tokens; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return tokens; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var token in tokens) { - await foreach (var token in tokens) + if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return token; - } + yield return token; } } } + } - /// - /// Retrieves the list of tokens corresponding to the specified application identifier. - /// - /// The application identifier associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified application. - public virtual IAsyncEnumerable FindByApplicationIdAsync( - string identifier, CancellationToken cancellationToken = default) + /// + /// Retrieves the list of tokens corresponding to the specified application identifier. + /// + /// The application identifier associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified application. + public virtual IAsyncEnumerable FindByApplicationIdAsync( + string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - var tokens = Options.CurrentValue.DisableEntityCaching ? - Store.FindByApplicationIdAsync(identifier, cancellationToken) : - Cache.FindByApplicationIdAsync(identifier, cancellationToken); + var tokens = Options.CurrentValue.DisableEntityCaching ? + Store.FindByApplicationIdAsync(identifier, cancellationToken) : + Cache.FindByApplicationIdAsync(identifier, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return tokens; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return tokens; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var token in tokens) { - await foreach (var token in tokens) + if (string.Equals(await Store.GetApplicationIdAsync(token, cancellationToken), identifier, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetApplicationIdAsync(token, cancellationToken), identifier, StringComparison.Ordinal)) - { - yield return token; - } + yield return token; } } } + } - /// - /// Retrieves the list of tokens corresponding to the specified authorization identifier. - /// - /// The authorization identifier associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified authorization. - public virtual IAsyncEnumerable FindByAuthorizationIdAsync( - string identifier, CancellationToken cancellationToken = default) + /// + /// Retrieves the list of tokens corresponding to the specified authorization identifier. + /// + /// The authorization identifier associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified authorization. + public virtual IAsyncEnumerable FindByAuthorizationIdAsync( + string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - var tokens = Options.CurrentValue.DisableEntityCaching ? - Store.FindByAuthorizationIdAsync(identifier, cancellationToken) : - Cache.FindByAuthorizationIdAsync(identifier, cancellationToken); + var tokens = Options.CurrentValue.DisableEntityCaching ? + Store.FindByAuthorizationIdAsync(identifier, cancellationToken) : + Cache.FindByAuthorizationIdAsync(identifier, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return tokens; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return tokens; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var token in tokens) { - await foreach (var token in tokens) + if (string.Equals(await Store.GetAuthorizationIdAsync(token, cancellationToken), identifier, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetAuthorizationIdAsync(token, cancellationToken), identifier, StringComparison.Ordinal)) - { - yield return token; - } + yield return token; } } } + } - /// - /// Retrieves a token using its unique identifier. - /// - /// The unique identifier associated with the token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the token corresponding to the unique identifier. - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default) + /// + /// Retrieves a token using its unique identifier. + /// + /// The unique identifier associated with the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token corresponding to the unique identifier. + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } - - var token = Options.CurrentValue.DisableEntityCaching ? - await Store.FindByIdAsync(identifier, cancellationToken) : - await Cache.FindByIdAsync(identifier, cancellationToken); - - if (token is null) - { - return null; - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - if (!Options.CurrentValue.DisableAdditionalFiltering && - !string.Equals(await Store.GetIdAsync(token, cancellationToken), identifier, StringComparison.Ordinal)) - { - return null; - } + var token = Options.CurrentValue.DisableEntityCaching ? + await Store.FindByIdAsync(identifier, cancellationToken) : + await Cache.FindByIdAsync(identifier, cancellationToken); - return token; + if (token is null) + { + return null; } - /// - /// Retrieves the list of tokens corresponding to the specified reference identifier. - /// Note: the reference identifier may be hashed or encrypted for security reasons. - /// - /// The reference identifier associated with the tokens. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the tokens corresponding to the specified reference identifier. - /// - public virtual async ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken = default) + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + if (!Options.CurrentValue.DisableAdditionalFiltering && + !string.Equals(await Store.GetIdAsync(token, cancellationToken), identifier, StringComparison.Ordinal)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + return null; + } - identifier = await ObfuscateReferenceIdAsync(identifier, cancellationToken); + return token; + } - var token = Options.CurrentValue.DisableEntityCaching ? - await Store.FindByReferenceIdAsync(identifier, cancellationToken) : - await Cache.FindByReferenceIdAsync(identifier, cancellationToken); + /// + /// Retrieves the list of tokens corresponding to the specified reference identifier. + /// Note: the reference identifier may be hashed or encrypted for security reasons. + /// + /// The reference identifier associated with the tokens. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens corresponding to the specified reference identifier. + /// + public virtual async ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - if (token is null) - { - return null; - } + identifier = await ObfuscateReferenceIdAsync(identifier, cancellationToken); - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + var token = Options.CurrentValue.DisableEntityCaching ? + await Store.FindByReferenceIdAsync(identifier, cancellationToken) : + await Cache.FindByReferenceIdAsync(identifier, cancellationToken); - if (!Options.CurrentValue.DisableAdditionalFiltering && - !string.Equals(await Store.GetReferenceIdAsync(token, cancellationToken), identifier, StringComparison.Ordinal)) - { - return null; - } + if (token is null) + { + return null; + } + + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return token; + if (!Options.CurrentValue.DisableAdditionalFiltering && + !string.Equals(await Store.GetReferenceIdAsync(token, cancellationToken), identifier, StringComparison.Ordinal)) + { + return null; } - /// - /// Retrieves the list of tokens corresponding to the specified subject. - /// - /// The subject associated with the tokens. - /// The that can be used to abort the operation. - /// The tokens corresponding to the specified subject. - public virtual IAsyncEnumerable FindBySubjectAsync( - string subject, CancellationToken cancellationToken = default) + return token; + } + + /// + /// Retrieves the list of tokens corresponding to the specified subject. + /// + /// The subject associated with the tokens. + /// The that can be used to abort the operation. + /// The tokens corresponding to the specified subject. + public virtual IAsyncEnumerable FindBySubjectAsync( + string subject, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - var tokens = Options.CurrentValue.DisableEntityCaching ? - Store.FindBySubjectAsync(subject, cancellationToken) : - Cache.FindBySubjectAsync(subject, cancellationToken); + var tokens = Options.CurrentValue.DisableEntityCaching ? + Store.FindBySubjectAsync(subject, cancellationToken) : + Cache.FindBySubjectAsync(subject, cancellationToken); - if (Options.CurrentValue.DisableAdditionalFiltering) - { - return tokens; - } + if (Options.CurrentValue.DisableAdditionalFiltering) + { + return tokens; + } - // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. - // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation - // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. + // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. + // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation + // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var token in tokens) { - await foreach (var token in tokens) + if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) { - if (string.Equals(await Store.GetSubjectAsync(token, cancellationToken), subject, StringComparison.Ordinal)) - { - yield return token; - } + yield return token; } } } + } + + /// + /// Retrieves the optional application identifier associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the application identifier associated with the token. + /// + public virtual ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetApplicationIdAsync(token, cancellationToken); + } + + /// + /// Executes the specified query and returns the first element. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + public virtual ValueTask GetAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } + + return GetAsync(static (tokens, query) => query(tokens), query, cancellationToken); + } + + /// + /// Executes the specified query and returns the first element. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the first element returned when executing the query. + /// + public virtual ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } + + return Store.GetAsync(query, state, cancellationToken); + } + + /// + /// Retrieves the optional authorization identifier associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorization identifier associated with the token. + /// + public virtual ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetAuthorizationIdAsync(token, cancellationToken); + } + + /// + /// Retrieves the creation date associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the creation date associated with the specified token. + /// + public virtual ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetCreationDateAsync(token, cancellationToken); + } + + /// + /// Retrieves the expiration date associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the expiration date associated with the specified token. + /// + public virtual ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetExpirationDateAsync(token, cancellationToken); + } + + /// + /// Retrieves the unique identifier associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the unique identifier associated with the token. + /// + public virtual ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetIdAsync(token, cancellationToken); + } + + /// + /// Retrieves the payload associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the payload associated with the specified token. + /// + public virtual ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetPayloadAsync(token, cancellationToken); + } + + /// + /// Retrieves the additional properties associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns all the additional properties associated with the token. + /// + public virtual ValueTask> GetPropertiesAsync( + TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetPropertiesAsync(token, cancellationToken); + } + + /// + /// Retrieves the redemption date associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the redemption date associated with the specified token. + /// + public virtual ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetRedemptionDateAsync(token, cancellationToken); + } + + /// + /// Retrieves the reference identifier associated with a token. + /// Note: depending on the manager used to create the token, + /// the reference identifier may be hashed for security reasons. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the reference identifier associated with the specified token. + /// + public virtual ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetReferenceIdAsync(token, cancellationToken); + } + + /// + /// Retrieves the status associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the status associated with the specified token. + /// + public virtual ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetStatusAsync(token, cancellationToken); + } + + /// + /// Retrieves the subject associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the subject associated with the specified token. + /// + public virtual ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + + return Store.GetSubjectAsync(token, cancellationToken); + } + + /// + /// Retrieves the token type associated with a token. + /// + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the token type associated with the specified token. + /// + public virtual ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - /// - /// Retrieves the optional application identifier associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the application identifier associated with the token. - /// - public virtual ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken = default) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return Store.GetTypeAsync(token, cancellationToken); + } - return Store.GetApplicationIdAsync(token, cancellationToken); + /// + /// Determines whether a given token has the specified status. + /// + /// The token. + /// The expected status. + /// The that can be used to abort the operation. + /// true if the token has the specified status, false otherwise. + public virtual async ValueTask HasStatusAsync(TToken token, string status, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - /// Executes the specified query and returns the first element. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - public virtual ValueTask GetAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(status)) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - return GetAsync(static (tokens, query) => query(tokens), query, cancellationToken); - } - - /// - /// Executes the specified query and returns the first element. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the first element returned when executing the query. - /// - public virtual ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + return string.Equals(await Store.GetStatusAsync(token, cancellationToken), status, StringComparison.OrdinalIgnoreCase); + } - return Store.GetAsync(query, state, cancellationToken); + /// + /// Determines whether a given token has the specified type. + /// + /// The token. + /// The expected type. + /// The that can be used to abort the operation. + /// true if the token has the specified type, false otherwise. + public virtual async ValueTask HasTypeAsync(TToken token, string type, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - /// Retrieves the optional authorization identifier associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization identifier associated with the token. - /// - public virtual ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(type)) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - return Store.GetAuthorizationIdAsync(token, cancellationToken); + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); } - /// - /// Retrieves the creation date associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the creation date associated with the specified token. - /// - public virtual ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken = default) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return string.Equals(await Store.GetTypeAsync(token, cancellationToken), type, StringComparison.OrdinalIgnoreCase); + } - return Store.GetCreationDateAsync(token, cancellationToken); + /// + /// Determines whether a given token has any of the specified types. + /// + /// The token. + /// The expected types. + /// The that can be used to abort the operation. + /// true if the token has any of the specified types, false otherwise. + public virtual async ValueTask HasTypeAsync(TToken token, ImmutableArray types, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - /// Retrieves the expiration date associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the expiration date associated with the specified token. - /// - public virtual ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken = default) + var type = await Store.GetTypeAsync(token, cancellationToken); + if (string.IsNullOrEmpty(type)) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - return Store.GetExpirationDateAsync(token, cancellationToken); + return false; } - /// - /// Retrieves the unique identifier associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the unique identifier associated with the token. - /// - public virtual ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken = default) + for (var index = 0; index < types.Length; index++) { - if (token is null) + if (string.Equals(type, types[index], StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentNullException(nameof(token)); + return true; } - - return Store.GetIdAsync(token, cancellationToken); } - /// - /// Retrieves the payload associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the payload associated with the specified token. - /// - public virtual ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken = default) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return false; + } - return Store.GetPayloadAsync(token, cancellationToken); - } + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The number of results to return. + /// The number of results to skip. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + int? count = null, int? offset = null, CancellationToken cancellationToken = default) + => Store.ListAsync(count, offset, cancellationToken); - /// - /// Retrieves the additional properties associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns all the additional properties associated with the token. - /// - public virtual ValueTask> GetPropertiesAsync( - TToken token, CancellationToken cancellationToken = default) + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The result type. + /// The query to execute. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + Func, IQueryable> query, CancellationToken cancellationToken = default) + { + if (query is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - return Store.GetPropertiesAsync(token, cancellationToken); + throw new ArgumentNullException(nameof(query)); } - /// - /// Retrieves the redemption date associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the redemption date associated with the specified token. - /// - public virtual ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken = default) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return ListAsync(static (tokens, query) => query(tokens), query, cancellationToken); + } - return Store.GetRedemptionDateAsync(token, cancellationToken); + /// + /// Executes the specified query and returns all the corresponding elements. + /// + /// The state type. + /// The result type. + /// The query to execute. + /// The optional state. + /// The that can be used to abort the operation. + /// All the elements returned when executing the specified query. + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken = default) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - /// Retrieves the reference identifier associated with a token. - /// Note: depending on the manager used to create the token, - /// the reference identifier may be hashed for security reasons. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the reference identifier associated with the specified token. - /// - public virtual ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken = default) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return Store.ListAsync(query, state, cancellationToken); + } - return Store.GetReferenceIdAsync(token, cancellationToken); + /// + /// Populates the token using the specified descriptor. + /// + /// The token. + /// The descriptor. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask PopulateAsync(TToken token, + OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - /// Retrieves the status associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the status associated with the specified token. - /// - public virtual ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken = default) + if (descriptor is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - return Store.GetStatusAsync(token, cancellationToken); + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Retrieves the subject associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the subject associated with the specified token. - /// - public virtual ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken = default) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + await Store.SetApplicationIdAsync(token, descriptor.ApplicationId, cancellationToken); + await Store.SetAuthorizationIdAsync(token, descriptor.AuthorizationId, cancellationToken); + await Store.SetCreationDateAsync(token, descriptor.CreationDate, cancellationToken); + await Store.SetExpirationDateAsync(token, descriptor.ExpirationDate, cancellationToken); + await Store.SetPayloadAsync(token, descriptor.Payload, cancellationToken); + await Store.SetPropertiesAsync(token, descriptor.Properties.ToImmutableDictionary(), cancellationToken); + await Store.SetRedemptionDateAsync(token, descriptor.RedemptionDate, cancellationToken); + await Store.SetReferenceIdAsync(token, descriptor.ReferenceId, cancellationToken); + await Store.SetStatusAsync(token, descriptor.Status, cancellationToken); + await Store.SetSubjectAsync(token, descriptor.Subject, cancellationToken); + await Store.SetTypeAsync(token, descriptor.Type, cancellationToken); + } - return Store.GetSubjectAsync(token, cancellationToken); + /// + /// Populates the specified descriptor using the properties exposed by the token. + /// + /// The descriptor. + /// The token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask PopulateAsync( + OpenIddictTokenDescriptor descriptor, + TToken token, CancellationToken cancellationToken = default) + { + if (descriptor is null) + { + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Retrieves the token type associated with a token. - /// - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation, - /// whose result returns the token type associated with the specified token. - /// - public virtual ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken = default) + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - return Store.GetTypeAsync(token, cancellationToken); + throw new ArgumentNullException(nameof(token)); } - /// - /// Determines whether a given token has the specified status. - /// - /// The token. - /// The expected status. - /// The that can be used to abort the operation. - /// true if the token has the specified status, false otherwise. - public virtual async ValueTask HasStatusAsync(TToken token, string status, CancellationToken cancellationToken = default) + descriptor.ApplicationId = await Store.GetApplicationIdAsync(token, cancellationToken); + descriptor.AuthorizationId = await Store.GetAuthorizationIdAsync(token, cancellationToken); + descriptor.CreationDate = await Store.GetCreationDateAsync(token, cancellationToken); + descriptor.ExpirationDate = await Store.GetExpirationDateAsync(token, cancellationToken); + descriptor.Payload = await Store.GetPayloadAsync(token, cancellationToken); + descriptor.RedemptionDate = await Store.GetRedemptionDateAsync(token, cancellationToken); + descriptor.ReferenceId = await Store.GetReferenceIdAsync(token, cancellationToken); + descriptor.Status = await Store.GetStatusAsync(token, cancellationToken); + descriptor.Subject = await Store.GetSubjectAsync(token, cancellationToken); + descriptor.Type = await Store.GetTypeAsync(token, cancellationToken); + + descriptor.Properties.Clear(); + foreach (var pair in await Store.GetPropertiesAsync(token, cancellationToken)) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - return string.Equals(await Store.GetStatusAsync(token, cancellationToken), status, StringComparison.OrdinalIgnoreCase); + descriptor.Properties.Add(pair.Key, pair.Value); } + } - /// - /// Determines whether a given token has the specified type. - /// - /// The token. - /// The expected type. - /// The that can be used to abort the operation. - /// true if the token has the specified type, false otherwise. - public virtual async ValueTask HasTypeAsync(TToken token, string type, CancellationToken cancellationToken = default) + /// + /// Removes the tokens that are marked as invalid or whose attached authorization is no longer valid. + /// Only tokens created before the specified are removed. + /// + /// The date before which tokens are not pruned. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default) + => Store.PruneAsync(threshold, cancellationToken); + /// + /// Tries to redeem a token. + /// + /// The token to redeem. + /// The that can be used to abort the operation. + /// true if the token was successfully redemeed, false otherwise. + public virtual async ValueTask TryRedeemAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } - - return string.Equals(await Store.GetTypeAsync(token, cancellationToken), type, StringComparison.OrdinalIgnoreCase); + throw new ArgumentNullException(nameof(token)); } - /// - /// Determines whether a given token has any of the specified types. - /// - /// The token. - /// The expected types. - /// The that can be used to abort the operation. - /// true if the token has any of the specified types, false otherwise. - public virtual async ValueTask HasTypeAsync(TToken token, ImmutableArray types, CancellationToken cancellationToken = default) + // If the token doesn't have a redemption date attached, this likely means it's + // the first time the token is redeemed. In this case, attach the current date. + if (await Store.GetRedemptionDateAsync(token, cancellationToken) is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - var type = await Store.GetTypeAsync(token, cancellationToken); - if (string.IsNullOrEmpty(type)) - { - return false; - } + await Store.SetRedemptionDateAsync(token, DateTimeOffset.UtcNow, cancellationToken); + } - for (var index = 0; index < types.Length; index++) - { - if (string.Equals(type, types[index], StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } + await Store.SetStatusAsync(token, Statuses.Redeemed, cancellationToken); - return false; - } + try + { + await UpdateAsync(token, cancellationToken); - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The number of results to return. - /// The number of results to skip. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - int? count = null, int? offset = null, CancellationToken cancellationToken = default) - => Store.ListAsync(count, offset, cancellationToken); - - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The result type. - /// The query to execute. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - Func, IQueryable> query, CancellationToken cancellationToken = default) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + Logger.LogInformation(SR.GetResourceString(SR.ID6168), await Store.GetIdAsync(token, cancellationToken)); - return ListAsync(static (tokens, query) => query(tokens), query, cancellationToken); + return true; } - /// - /// Executes the specified query and returns all the corresponding elements. - /// - /// The state type. - /// The result type. - /// The query to execute. - /// The optional state. - /// The that can be used to abort the operation. - /// All the elements returned when executing the specified query. - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken = default) + catch (ConcurrencyException exception) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + Logger.LogDebug(exception, SR.GetResourceString(SR.ID6169), await Store.GetIdAsync(token, cancellationToken)); - return Store.ListAsync(query, state, cancellationToken); + return false; } - /// - /// Populates the token using the specified descriptor. - /// - /// The token. - /// The descriptor. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask PopulateAsync(TToken token, - OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default) + catch (Exception exception) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - await Store.SetApplicationIdAsync(token, descriptor.ApplicationId, cancellationToken); - await Store.SetAuthorizationIdAsync(token, descriptor.AuthorizationId, cancellationToken); - await Store.SetCreationDateAsync(token, descriptor.CreationDate, cancellationToken); - await Store.SetExpirationDateAsync(token, descriptor.ExpirationDate, cancellationToken); - await Store.SetPayloadAsync(token, descriptor.Payload, cancellationToken); - await Store.SetPropertiesAsync(token, descriptor.Properties.ToImmutableDictionary(), cancellationToken); - await Store.SetRedemptionDateAsync(token, descriptor.RedemptionDate, cancellationToken); - await Store.SetReferenceIdAsync(token, descriptor.ReferenceId, cancellationToken); - await Store.SetStatusAsync(token, descriptor.Status, cancellationToken); - await Store.SetSubjectAsync(token, descriptor.Subject, cancellationToken); - await Store.SetTypeAsync(token, descriptor.Type, cancellationToken); - } - - /// - /// Populates the specified descriptor using the properties exposed by the token. - /// - /// The descriptor. - /// The token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask PopulateAsync( - OpenIddictTokenDescriptor descriptor, - TToken token, CancellationToken cancellationToken = default) - { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + Logger.LogWarning(exception, SR.GetResourceString(SR.ID6170), await Store.GetIdAsync(token, cancellationToken)); - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - descriptor.ApplicationId = await Store.GetApplicationIdAsync(token, cancellationToken); - descriptor.AuthorizationId = await Store.GetAuthorizationIdAsync(token, cancellationToken); - descriptor.CreationDate = await Store.GetCreationDateAsync(token, cancellationToken); - descriptor.ExpirationDate = await Store.GetExpirationDateAsync(token, cancellationToken); - descriptor.Payload = await Store.GetPayloadAsync(token, cancellationToken); - descriptor.RedemptionDate = await Store.GetRedemptionDateAsync(token, cancellationToken); - descriptor.ReferenceId = await Store.GetReferenceIdAsync(token, cancellationToken); - descriptor.Status = await Store.GetStatusAsync(token, cancellationToken); - descriptor.Subject = await Store.GetSubjectAsync(token, cancellationToken); - descriptor.Type = await Store.GetTypeAsync(token, cancellationToken); - - descriptor.Properties.Clear(); - foreach (var pair in await Store.GetPropertiesAsync(token, cancellationToken)) - { - descriptor.Properties.Add(pair.Key, pair.Value); - } + return false; } + } - /// - /// Removes the tokens that are marked as invalid or whose attached authorization is no longer valid. - /// Only tokens created before the specified are removed. - /// - /// The date before which tokens are not pruned. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default) - => Store.PruneAsync(threshold, cancellationToken); - /// - /// Tries to redeem a token. - /// - /// The token to redeem. - /// The that can be used to abort the operation. - /// true if the token was successfully redemeed, false otherwise. - public virtual async ValueTask TryRedeemAsync(TToken token, CancellationToken cancellationToken = default) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - // If the token doesn't have a redemption date attached, this likely means it's - // the first time the token is redeemed. In this case, attach the current date. - if (await Store.GetRedemptionDateAsync(token, cancellationToken) is null) - { - await Store.SetRedemptionDateAsync(token, DateTimeOffset.UtcNow, cancellationToken); - } + /// + /// Tries to reject a token. + /// + /// The token to reject. + /// The that can be used to abort the operation. + /// true if the token was successfully redemeed, false otherwise. + public virtual async ValueTask TryRejectAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - await Store.SetStatusAsync(token, Statuses.Redeemed, cancellationToken); + await Store.SetStatusAsync(token, Statuses.Rejected, cancellationToken); - try - { - await UpdateAsync(token, cancellationToken); + try + { + await UpdateAsync(token, cancellationToken); - Logger.LogInformation(SR.GetResourceString(SR.ID6168), await Store.GetIdAsync(token, cancellationToken)); + Logger.LogInformation(SR.GetResourceString(SR.ID6171), await Store.GetIdAsync(token, cancellationToken)); - return true; - } + return true; + } - catch (ConcurrencyException exception) - { - Logger.LogDebug(exception, SR.GetResourceString(SR.ID6169), await Store.GetIdAsync(token, cancellationToken)); + catch (ConcurrencyException exception) + { + Logger.LogDebug(exception, SR.GetResourceString(SR.ID6172), await Store.GetIdAsync(token, cancellationToken)); - return false; - } + return false; + } - catch (Exception exception) - { - Logger.LogWarning(exception, SR.GetResourceString(SR.ID6170), await Store.GetIdAsync(token, cancellationToken)); + catch (Exception exception) + { + Logger.LogWarning(exception, SR.GetResourceString(SR.ID6173), await Store.GetIdAsync(token, cancellationToken)); - return false; - } + return false; } + } - /// - /// Tries to reject a token. - /// - /// The token to reject. - /// The that can be used to abort the operation. - /// true if the token was successfully redemeed, false otherwise. - public virtual async ValueTask TryRejectAsync(TToken token, CancellationToken cancellationToken = default) + /// + /// Tries to revoke a token. + /// + /// The token to revoke. + /// The that can be used to abort the operation. + /// true if the token was successfully revoked, false otherwise. + public virtual async ValueTask TryRevokeAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - await Store.SetStatusAsync(token, Statuses.Rejected, cancellationToken); + await Store.SetStatusAsync(token, Statuses.Revoked, cancellationToken); - try - { - await UpdateAsync(token, cancellationToken); + try + { + await UpdateAsync(token, cancellationToken); - Logger.LogInformation(SR.GetResourceString(SR.ID6171), await Store.GetIdAsync(token, cancellationToken)); + Logger.LogInformation(SR.GetResourceString(SR.ID6174), await Store.GetIdAsync(token, cancellationToken)); - return true; - } + return true; + } - catch (ConcurrencyException exception) - { - Logger.LogDebug(exception, SR.GetResourceString(SR.ID6172), await Store.GetIdAsync(token, cancellationToken)); + catch (ConcurrencyException exception) + { + Logger.LogDebug(exception, SR.GetResourceString(SR.ID6175), await Store.GetIdAsync(token, cancellationToken)); - return false; - } + return false; + } - catch (Exception exception) - { - Logger.LogWarning(exception, SR.GetResourceString(SR.ID6173), await Store.GetIdAsync(token, cancellationToken)); + catch (Exception exception) + { + Logger.LogWarning(exception, SR.GetResourceString(SR.ID6176), await Store.GetIdAsync(token, cancellationToken)); - return false; - } + return false; } + } - /// - /// Tries to revoke a token. - /// - /// The token to revoke. - /// The that can be used to abort the operation. - /// true if the token was successfully revoked, false otherwise. - public virtual async ValueTask TryRevokeAsync(TToken token, CancellationToken cancellationToken = default) + /// + /// Updates an existing token. + /// + /// The token to update. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken = default) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - await Store.SetStatusAsync(token, Statuses.Revoked, cancellationToken); + var results = await GetValidationResultsAsync(token, cancellationToken); + if (results.Any(result => result != ValidationResult.Success)) + { + var builder = new StringBuilder(); + builder.AppendLine(SR.GetResourceString(SR.ID0227)); + builder.AppendLine(); - try + foreach (var result in results) { - await UpdateAsync(token, cancellationToken); - - Logger.LogInformation(SR.GetResourceString(SR.ID6174), await Store.GetIdAsync(token, cancellationToken)); - - return true; + builder.AppendLine(result.ErrorMessage); } - catch (ConcurrencyException exception) - { - Logger.LogDebug(exception, SR.GetResourceString(SR.ID6175), await Store.GetIdAsync(token, cancellationToken)); - - return false; - } + throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); + } - catch (Exception exception) - { - Logger.LogWarning(exception, SR.GetResourceString(SR.ID6176), await Store.GetIdAsync(token, cancellationToken)); + await Store.UpdateAsync(token, cancellationToken); - return false; - } + if (!Options.CurrentValue.DisableEntityCaching) + { + await Cache.RemoveAsync(token, cancellationToken); + await Cache.AddAsync(token, cancellationToken); } - /// - /// Updates an existing token. - /// - /// The token to update. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken = default) + async Task> GetValidationResultsAsync( + TToken token, CancellationToken cancellationToken) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - var results = await GetValidationResultsAsync(token, cancellationToken); - if (results.Any(result => result != ValidationResult.Success)) - { - var builder = new StringBuilder(); - builder.AppendLine(SR.GetResourceString(SR.ID0227)); - builder.AppendLine(); + var builder = ImmutableArray.CreateBuilder(); - foreach (var result in results) - { - builder.AppendLine(result.ErrorMessage); - } - - throw new OpenIddictExceptions.ValidationException(builder.ToString(), results); - } - - await Store.UpdateAsync(token, cancellationToken); - - if (!Options.CurrentValue.DisableEntityCaching) + await foreach (var result in ValidateAsync(token, cancellationToken)) { - await Cache.RemoveAsync(token, cancellationToken); - await Cache.AddAsync(token, cancellationToken); + builder.Add(result); } - async Task> GetValidationResultsAsync( - TToken token, CancellationToken cancellationToken) - { - var builder = ImmutableArray.CreateBuilder(); - - await foreach (var result in ValidateAsync(token, cancellationToken)) - { - builder.Add(result); - } + return builder.ToImmutable(); + } + } - return builder.ToImmutable(); - } + /// + /// Updates an existing token. + /// + /// The token to update. + /// The descriptor used to update the token. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual async ValueTask UpdateAsync(TToken token, + OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - /// Updates an existing token. - /// - /// The token to update. - /// The descriptor used to update the token. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public virtual async ValueTask UpdateAsync(TToken token, - OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken = default) + if (descriptor is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + // Store the original reference identifier for later comparison. + var comparand = await Store.GetReferenceIdAsync(token, cancellationToken); + await PopulateAsync(token, descriptor, cancellationToken); - // Store the original reference identifier for later comparison. - var comparand = await Store.GetReferenceIdAsync(token, cancellationToken); - await PopulateAsync(token, descriptor, cancellationToken); + // If the reference identifier was updated, re-obfuscate it before persisting the changes. + var identifier = await Store.GetReferenceIdAsync(token, cancellationToken); + if (!string.IsNullOrEmpty(identifier) && !string.Equals(identifier, comparand, StringComparison.Ordinal)) + { + identifier = await ObfuscateReferenceIdAsync(identifier, cancellationToken); + await Store.SetReferenceIdAsync(token, identifier, cancellationToken); + } - // If the reference identifier was updated, re-obfuscate it before persisting the changes. - var identifier = await Store.GetReferenceIdAsync(token, cancellationToken); - if (!string.IsNullOrEmpty(identifier) && !string.Equals(identifier, comparand, StringComparison.Ordinal)) - { - identifier = await ObfuscateReferenceIdAsync(identifier, cancellationToken); - await Store.SetReferenceIdAsync(token, identifier, cancellationToken); - } + await UpdateAsync(token, cancellationToken); + } - await UpdateAsync(token, cancellationToken); + /// + /// Validates the token to ensure it's in a consistent state. + /// + /// The token. + /// The that can be used to abort the operation. + /// The validation error encountered when validating the token. + public virtual async IAsyncEnumerable ValidateAsync( + TToken token, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - /// Validates the token to ensure it's in a consistent state. - /// - /// The token. - /// The that can be used to abort the operation. - /// The validation error encountered when validating the token. - public virtual async IAsyncEnumerable ValidateAsync( - TToken token, [EnumeratorCancellation] CancellationToken cancellationToken = default) + // If a reference identifier was associated with the token, + // ensure it's not already used for a different token. + var identifier = await Store.GetReferenceIdAsync(token, cancellationToken); + if (!string.IsNullOrEmpty(identifier)) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - // If a reference identifier was associated with the token, - // ensure it's not already used for a different token. - var identifier = await Store.GetReferenceIdAsync(token, cancellationToken); - if (!string.IsNullOrEmpty(identifier)) - { - // Note: depending on the database/table/query collation used by the store, a reference token - // whose identifier doesn't exactly match the specified value may be returned (e.g because - // the casing is different). To avoid issues when the reference identifier is part of an index - // using the same collation, an error is added even if the two identifiers don't exactly match. - var other = await Store.FindByReferenceIdAsync(identifier, cancellationToken); - if (other is not null && !string.Equals( - await Store.GetIdAsync(other, cancellationToken), - await Store.GetIdAsync(token, cancellationToken), StringComparison.Ordinal)) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2085)); - } - } - - var type = await Store.GetTypeAsync(token, cancellationToken); - if (string.IsNullOrEmpty(type)) + // Note: depending on the database/table/query collation used by the store, a reference token + // whose identifier doesn't exactly match the specified value may be returned (e.g because + // the casing is different). To avoid issues when the reference identifier is part of an index + // using the same collation, an error is added even if the two identifiers don't exactly match. + var other = await Store.FindByReferenceIdAsync(identifier, cancellationToken); + if (other is not null && !string.Equals( + await Store.GetIdAsync(other, cancellationToken), + await Store.GetIdAsync(token, cancellationToken), StringComparison.Ordinal)) { - yield return new ValidationResult(SR.GetResourceString(SR.ID2086)); + yield return new ValidationResult(SR.GetResourceString(SR.ID2085)); } + } - if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken))) - { - yield return new ValidationResult(SR.GetResourceString(SR.ID2038)); - } + var type = await Store.GetTypeAsync(token, cancellationToken); + if (string.IsNullOrEmpty(type)) + { + yield return new ValidationResult(SR.GetResourceString(SR.ID2086)); } - /// - /// Obfuscates the specified reference identifier so it can be safely stored in a database. - /// By default, this method returns a simple hashed representation computed using SHA256. - /// - /// The client identifier. - /// The that can be used to abort the operation. - /// - /// A that can be used to monitor the asynchronous operation. - /// - protected virtual ValueTask ObfuscateReferenceIdAsync(string identifier, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken))) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + yield return new ValidationResult(SR.GetResourceString(SR.ID2038)); + } + } - // Compute the digest of the generated identifier and use it as the hashed identifier of the reference token. - // Doing that prevents token identifiers stolen from the database from being used as valid reference tokens. - using var algorithm = SHA256.Create(); - return new ValueTask(Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(identifier)))); + /// + /// Obfuscates the specified reference identifier so it can be safely stored in a database. + /// By default, this method returns a simple hashed representation computed using SHA256. + /// + /// The client identifier. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + protected virtual ValueTask ObfuscateReferenceIdAsync(string identifier, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - ValueTask IOpenIddictTokenManager.CountAsync(CancellationToken cancellationToken) - => CountAsync(cancellationToken); + // Compute the digest of the generated identifier and use it as the hashed identifier of the reference token. + // Doing that prevents token identifiers stolen from the database from being used as valid reference tokens. + using var algorithm = SHA256.Create(); + return new ValueTask(Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(identifier)))); + } + + /// + ValueTask IOpenIddictTokenManager.CountAsync(CancellationToken cancellationToken) + => CountAsync(cancellationToken); - /// - ValueTask IOpenIddictTokenManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - => CountAsync(query, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) + => CountAsync(query, cancellationToken); - /// - async ValueTask IOpenIddictTokenManager.CreateAsync(OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) - => await CreateAsync(descriptor, cancellationToken); + /// + async ValueTask IOpenIddictTokenManager.CreateAsync(OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) + => await CreateAsync(descriptor, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.CreateAsync(object token, CancellationToken cancellationToken) - => CreateAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.CreateAsync(object token, CancellationToken cancellationToken) + => CreateAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.DeleteAsync(object token, CancellationToken cancellationToken) - => DeleteAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.DeleteAsync(object token, CancellationToken cancellationToken) + => DeleteAsync((TToken) token, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string subject, string client, CancellationToken cancellationToken) - => FindAsync(subject, client, cancellationToken); + /// + IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string subject, string client, CancellationToken cancellationToken) + => FindAsync(subject, client, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string subject, string client, string status, CancellationToken cancellationToken) - => FindAsync(subject, client, status, cancellationToken); + /// + IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string subject, string client, string status, CancellationToken cancellationToken) + => FindAsync(subject, client, status, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) - => FindAsync(subject, client, status, type, cancellationToken); + /// + IAsyncEnumerable IOpenIddictTokenManager.FindAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + => FindAsync(subject, client, status, type, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) - => FindByApplicationIdAsync(identifier, cancellationToken); + /// + IAsyncEnumerable IOpenIddictTokenManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) + => FindByApplicationIdAsync(identifier, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) - => FindByAuthorizationIdAsync(identifier, cancellationToken); + /// + IAsyncEnumerable IOpenIddictTokenManager.FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) + => FindByAuthorizationIdAsync(identifier, cancellationToken); - /// - async ValueTask IOpenIddictTokenManager.FindByIdAsync(string identifier, CancellationToken cancellationToken) - => await FindByIdAsync(identifier, cancellationToken); + /// + async ValueTask IOpenIddictTokenManager.FindByIdAsync(string identifier, CancellationToken cancellationToken) + => await FindByIdAsync(identifier, cancellationToken); - /// - async ValueTask IOpenIddictTokenManager.FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) - => await FindByReferenceIdAsync(identifier, cancellationToken); + /// + async ValueTask IOpenIddictTokenManager.FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) + => await FindByReferenceIdAsync(identifier, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.FindBySubjectAsync(string subject, CancellationToken cancellationToken) - => FindBySubjectAsync(subject, cancellationToken); + /// + IAsyncEnumerable IOpenIddictTokenManager.FindBySubjectAsync(string subject, CancellationToken cancellationToken) + => FindBySubjectAsync(subject, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetApplicationIdAsync(object token, CancellationToken cancellationToken) - => GetApplicationIdAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetApplicationIdAsync(object token, CancellationToken cancellationToken) + => GetApplicationIdAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetAsync(Func, IQueryable> query, CancellationToken cancellationToken) where TResult : default - => GetAsync(query, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetAsync(Func, IQueryable> query, CancellationToken cancellationToken) where TResult : default + => GetAsync(query, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) where TResult : default - => GetAsync(query, state, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) where TResult : default + => GetAsync(query, state, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetAuthorizationIdAsync(object token, CancellationToken cancellationToken) - => GetAuthorizationIdAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetAuthorizationIdAsync(object token, CancellationToken cancellationToken) + => GetAuthorizationIdAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetCreationDateAsync(object token, CancellationToken cancellationToken) - => GetCreationDateAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetCreationDateAsync(object token, CancellationToken cancellationToken) + => GetCreationDateAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetExpirationDateAsync(object token, CancellationToken cancellationToken) - => GetExpirationDateAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetExpirationDateAsync(object token, CancellationToken cancellationToken) + => GetExpirationDateAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetIdAsync(object token, CancellationToken cancellationToken) - => GetIdAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetIdAsync(object token, CancellationToken cancellationToken) + => GetIdAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetPayloadAsync(object token, CancellationToken cancellationToken) - => GetPayloadAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetPayloadAsync(object token, CancellationToken cancellationToken) + => GetPayloadAsync((TToken) token, cancellationToken); - /// - ValueTask> IOpenIddictTokenManager.GetPropertiesAsync(object token, CancellationToken cancellationToken) - => GetPropertiesAsync((TToken) token, cancellationToken); + /// + ValueTask> IOpenIddictTokenManager.GetPropertiesAsync(object token, CancellationToken cancellationToken) + => GetPropertiesAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetRedemptionDateAsync(object token, CancellationToken cancellationToken) - => GetRedemptionDateAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetRedemptionDateAsync(object token, CancellationToken cancellationToken) + => GetRedemptionDateAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetReferenceIdAsync(object token, CancellationToken cancellationToken) - => GetReferenceIdAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetReferenceIdAsync(object token, CancellationToken cancellationToken) + => GetReferenceIdAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetStatusAsync(object token, CancellationToken cancellationToken) - => GetStatusAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetStatusAsync(object token, CancellationToken cancellationToken) + => GetStatusAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetSubjectAsync(object token, CancellationToken cancellationToken) - => GetSubjectAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetSubjectAsync(object token, CancellationToken cancellationToken) + => GetSubjectAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.GetTypeAsync(object token, CancellationToken cancellationToken) - => GetTypeAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.GetTypeAsync(object token, CancellationToken cancellationToken) + => GetTypeAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.HasStatusAsync(object token, string status, CancellationToken cancellationToken) - => HasStatusAsync((TToken) token, status, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.HasStatusAsync(object token, string status, CancellationToken cancellationToken) + => HasStatusAsync((TToken) token, status, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.HasTypeAsync(object token, string type, CancellationToken cancellationToken) - => HasTypeAsync((TToken) token, type, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.HasTypeAsync(object token, string type, CancellationToken cancellationToken) + => HasTypeAsync((TToken) token, type, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.HasTypeAsync(object token, ImmutableArray types, CancellationToken cancellationToken) - => HasTypeAsync((TToken) token, types, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.HasTypeAsync(object token, ImmutableArray types, CancellationToken cancellationToken) + => HasTypeAsync((TToken) token, types, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) - => ListAsync(count, offset, cancellationToken); + /// + IAsyncEnumerable IOpenIddictTokenManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) + => ListAsync(count, offset, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.ListAsync(Func, IQueryable> query, CancellationToken cancellationToken) - => ListAsync(query, cancellationToken); + /// + IAsyncEnumerable IOpenIddictTokenManager.ListAsync(Func, IQueryable> query, CancellationToken cancellationToken) + => ListAsync(query, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.ListAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) - => ListAsync(query, state, cancellationToken); + /// + IAsyncEnumerable IOpenIddictTokenManager.ListAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) + => ListAsync(query, state, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.PopulateAsync(OpenIddictTokenDescriptor descriptor, object token, CancellationToken cancellationToken) - => PopulateAsync(descriptor, (TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.PopulateAsync(OpenIddictTokenDescriptor descriptor, object token, CancellationToken cancellationToken) + => PopulateAsync(descriptor, (TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.PopulateAsync(object token, OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) - => PopulateAsync((TToken) token, descriptor, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.PopulateAsync(object token, OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) + => PopulateAsync((TToken) token, descriptor, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) - => PruneAsync(threshold, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + => PruneAsync(threshold, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.TryRedeemAsync(object token, CancellationToken cancellationToken) - => TryRedeemAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.TryRedeemAsync(object token, CancellationToken cancellationToken) + => TryRedeemAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.TryRejectAsync(object token, CancellationToken cancellationToken) - => TryRejectAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.TryRejectAsync(object token, CancellationToken cancellationToken) + => TryRejectAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.TryRevokeAsync(object token, CancellationToken cancellationToken) - => TryRevokeAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.TryRevokeAsync(object token, CancellationToken cancellationToken) + => TryRevokeAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.UpdateAsync(object token, CancellationToken cancellationToken) - => UpdateAsync((TToken) token, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.UpdateAsync(object token, CancellationToken cancellationToken) + => UpdateAsync((TToken) token, cancellationToken); - /// - ValueTask IOpenIddictTokenManager.UpdateAsync(object token, OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) - => UpdateAsync((TToken) token, descriptor, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.UpdateAsync(object token, OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken) + => UpdateAsync((TToken) token, descriptor, cancellationToken); - /// - IAsyncEnumerable IOpenIddictTokenManager.ValidateAsync(object token, CancellationToken cancellationToken) - => ValidateAsync((TToken) token, cancellationToken); - } -} \ No newline at end of file + /// + IAsyncEnumerable IOpenIddictTokenManager.ValidateAsync(object token, CancellationToken cancellationToken) + => ValidateAsync((TToken) token, cancellationToken); +} diff --git a/src/OpenIddict.Core/OpenIddictCoreBuilder.cs b/src/OpenIddict.Core/OpenIddictCoreBuilder.cs index 455fe36b..3e3a270c 100644 --- a/src/OpenIddict.Core/OpenIddictCoreBuilder.cs +++ b/src/OpenIddict.Core/OpenIddictCoreBuilder.cs @@ -12,798 +12,797 @@ using OpenIddict.Core; using OpenIddict.Extensions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the OpenIddict core services. +/// +public class OpenIddictCoreBuilder { /// - /// Exposes the necessary methods required to configure the OpenIddict core services. + /// Initializes a new instance of . /// - public class OpenIddictCoreBuilder - { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictCoreBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Amends the default OpenIddict core configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictCoreBuilder Configure(Action configuration) - { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + /// The services collection. + public OpenIddictCoreBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); - Services.Configure(configuration); - - return this; - } - - /// - /// Adds a custom application store by a custom implementation derived - /// from . - /// Note: when using this overload, the application store - /// must be either a non-generic or closed generic service. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder AddApplicationStore(ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TStore : class - => AddApplicationStore(typeof(TStore), lifetime); - - /// - /// Adds a custom application store by a custom implementation derived - /// from . - /// Note: when using this overload, the application store can be - /// either a non-generic, a closed or an open generic service. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder AddApplicationStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } - var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictApplicationStore<>)); - if (root is null) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + /// + /// Amends the default OpenIddict core configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictCoreBuilder Configure(Action configuration) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - // Note: managers can be either open generics (e.g OpenIddictApplicationStore<>) - // or closed generics (e.g OpenIddictApplicationStore). - if (type.IsGenericTypeDefinition) - { - if (type.GetGenericArguments() is not { Length: 1 }) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + Services.Configure(configuration); - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStore<>), type, lifetime)); - } + return this; + } - else - { - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStore<>) - .MakeGenericType(root.GenericTypeArguments[0]), type, lifetime)); - } + /// + /// Adds a custom application store by a custom implementation derived + /// from . + /// Note: when using this overload, the application store + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder AddApplicationStore(ServiceLifetime lifetime = ServiceLifetime.Scoped) + where TStore : class + => AddApplicationStore(typeof(TStore), lifetime); - return this; - } - - /// - /// Adds a custom authorization store by a custom implementation derived - /// from . - /// Note: when using this overload, the authorization store - /// must be either a non-generic or closed generic service. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder AddAuthorizationStore(ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TStore : class - => AddAuthorizationStore(typeof(TStore), lifetime); - - /// - /// Adds a custom authorization store by a custom implementation derived - /// from . - /// Note: when using this overload, the authorization store can be - /// either a non-generic, a closed or an open generic service. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder AddAuthorizationStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + /// + /// Adds a custom application store by a custom implementation derived + /// from . + /// Note: when using this overload, the application store can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder AddApplicationStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } - var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictAuthorizationStore<>)); - if (root is null) + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictApplicationStore<>)); + if (root is null) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictApplicationStore<>) + // or closed generics (e.g OpenIddictApplicationStore). + if (type.IsGenericTypeDefinition) + { + if (type.GetGenericArguments() is not { Length: 1 }) { throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); } - // Note: managers can be either open generics (e.g OpenIddictAuthorizationStore<>) - // or closed generics (e.g OpenIddictAuthorizationStore). - if (type.IsGenericTypeDefinition) - { - if (type.GetGenericArguments() is not { Length: 1 }) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStore<>), type, lifetime)); + } - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStore<>), type, lifetime)); - } + else + { + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStore<>) + .MakeGenericType(root.GenericTypeArguments[0]), type, lifetime)); + } - else - { - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStore<>) - .MakeGenericType(root.GenericTypeArguments[0]), type, lifetime)); - } + return this; + } - return this; - } - - /// - /// Adds a custom scope store by a custom implementation derived - /// from . - /// Note: when using this overload, the scope store - /// must be either a non-generic or closed generic service. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder AddScopeStore(ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TStore : class - => AddScopeStore(typeof(TStore), lifetime); - - /// - /// Adds a custom scope store by a custom implementation derived - /// from . - /// Note: when using this overload, the scope store can be - /// either a non-generic, a closed or an open generic service. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder AddScopeStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + /// + /// Adds a custom authorization store by a custom implementation derived + /// from . + /// Note: when using this overload, the authorization store + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder AddAuthorizationStore(ServiceLifetime lifetime = ServiceLifetime.Scoped) + where TStore : class + => AddAuthorizationStore(typeof(TStore), lifetime); - var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictScopeStore<>)); - if (root is null) + /// + /// Adds a custom authorization store by a custom implementation derived + /// from . + /// Note: when using this overload, the authorization store can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder AddAuthorizationStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictAuthorizationStore<>)); + if (root is null) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictAuthorizationStore<>) + // or closed generics (e.g OpenIddictAuthorizationStore). + if (type.IsGenericTypeDefinition) + { + if (type.GetGenericArguments() is not { Length: 1 }) { throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); } - // Note: managers can be either open generics (e.g OpenIddictScopeStore<>) - // or closed generics (e.g OpenIddictScopeStore). - if (type.IsGenericTypeDefinition) - { - if (type.GetGenericArguments() is not { Length: 1 }) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStore<>), type, lifetime)); + } - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStore<>), type, lifetime)); - } + else + { + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStore<>) + .MakeGenericType(root.GenericTypeArguments[0]), type, lifetime)); + } - else - { - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStore<>) - .MakeGenericType(root.GenericTypeArguments[0]), type, lifetime)); - } + return this; + } - return this; - } - - /// - /// Adds a custom token store by a custom implementation derived - /// from . - /// Note: when using this overload, the token store - /// must be either a non-generic or closed generic service. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder AddTokenStore(ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TStore : class - => AddTokenStore(typeof(TStore), lifetime); - - /// - /// Adds a custom token store by a custom implementation derived - /// from . - /// Note: when using this overload, the token store can be - /// either a non-generic, a closed or an open generic service. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder AddTokenStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + /// + /// Adds a custom scope store by a custom implementation derived + /// from . + /// Note: when using this overload, the scope store + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder AddScopeStore(ServiceLifetime lifetime = ServiceLifetime.Scoped) + where TStore : class + => AddScopeStore(typeof(TStore), lifetime); + + /// + /// Adds a custom scope store by a custom implementation derived + /// from . + /// Note: when using this overload, the scope store can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder AddScopeStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } - var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictTokenStore<>)); - if (root is null) + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictScopeStore<>)); + if (root is null) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictScopeStore<>) + // or closed generics (e.g OpenIddictScopeStore). + if (type.IsGenericTypeDefinition) + { + if (type.GetGenericArguments() is not { Length: 1 }) { throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); } - // Note: managers can be either open generics (e.g OpenIddictTokenStore<>) - // or closed generics (e.g OpenIddictTokenStore). - if (type.IsGenericTypeDefinition) - { - if (type.GetGenericArguments() is not { Length: 1 }) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStore<>), type, lifetime)); + } - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStore<>), type, lifetime)); - } + else + { + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStore<>) + .MakeGenericType(root.GenericTypeArguments[0]), type, lifetime)); + } - else - { - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStore<>) - .MakeGenericType(root.GenericTypeArguments[0]), type, lifetime)); - } + return this; + } - return this; - } - - /// - /// Replace the default application manager by a custom manager derived - /// from . - /// Note: when using this overload, the application manager - /// must be either a non-generic or closed generic service. - /// - /// The type of the custom manager. - /// The . - public OpenIddictCoreBuilder ReplaceApplicationManager() - where TManager : class - => ReplaceApplicationManager(typeof(TManager)); - - /// - /// Replace the default application manager by a custom manager derived - /// from . - /// Note: when using this overload, the application manager can be - /// either a non-generic, a closed or an open generic service. - /// - /// The type of the custom manager. - /// The . - public OpenIddictCoreBuilder ReplaceApplicationManager(Type type) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + /// + /// Adds a custom token store by a custom implementation derived + /// from . + /// Note: when using this overload, the token store + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder AddTokenStore(ServiceLifetime lifetime = ServiceLifetime.Scoped) + where TStore : class + => AddTokenStore(typeof(TStore), lifetime); - var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictApplicationManager<>)); - if (root is null) + /// + /// Adds a custom token store by a custom implementation derived + /// from . + /// Note: when using this overload, the token store can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder AddTokenStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictTokenStore<>)); + if (root is null) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictTokenStore<>) + // or closed generics (e.g OpenIddictTokenStore). + if (type.IsGenericTypeDefinition) + { + if (type.GetGenericArguments() is not { Length: 1 }) { throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); } - // Note: managers can be either open generics (e.g OpenIddictApplicationManager<>) - // or closed generics (e.g OpenIddictApplicationManager). - if (type.IsGenericTypeDefinition) - { - if (type.GetGenericArguments() is not { Length: 1 }) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStore<>), type, lifetime)); + } - Services.Replace(ServiceDescriptor.Scoped(type, type)); - Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>), type)); - } + else + { + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStore<>) + .MakeGenericType(root.GenericTypeArguments[0]), type, lifetime)); + } - else - { - object ResolveManager(IServiceProvider provider) - => provider.GetRequiredService(typeof(OpenIddictApplicationManager<>) - .MakeGenericType(root.GenericTypeArguments[0])); + return this; + } - Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager)); - Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>) - .MakeGenericType(root.GenericTypeArguments[0]), type)); - } + /// + /// Replace the default application manager by a custom manager derived + /// from . + /// Note: when using this overload, the application manager + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceApplicationManager() + where TManager : class + => ReplaceApplicationManager(typeof(TManager)); - return this; - } - - /// - /// Replaces the default application store resolver by a custom implementation. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder ReplaceApplicationStoreResolver(ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TResolver : IOpenIddictApplicationStoreResolver - => ReplaceApplicationStoreResolver(typeof(TResolver), lifetime); - - /// - /// Replaces the default application store resolver by a custom implementation. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder ReplaceApplicationStoreResolver( - Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + /// + /// Replace the default application manager by a custom manager derived + /// from . + /// Note: when using this overload, the application manager can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceApplicationManager(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictApplicationManager<>)); + if (root is null) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - if (!typeof(IOpenIddictApplicationStoreResolver).IsAssignableFrom(type)) + // Note: managers can be either open generics (e.g OpenIddictApplicationManager<>) + // or closed generics (e.g OpenIddictApplicationManager). + if (type.IsGenericTypeDefinition) + { + if (type.GetGenericArguments() is not { Length: 1 }) { throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); } - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStoreResolver), type, lifetime)); - - return this; - } - - /// - /// Replace the default authorization manager by a custom manager derived - /// from . - /// Note: when using this overload, the authorization manager - /// must be either a non-generic or closed generic service. - /// - /// The type of the custom manager. - /// The . - public OpenIddictCoreBuilder ReplaceAuthorizationManager() - where TManager : class - => ReplaceAuthorizationManager(typeof(TManager)); - - /// - /// Replace the default authorization manager by a custom manager derived - /// from . - /// Note: when using this overload, the authorization manager can be - /// either a non-generic, a closed or an open generic service. - /// - /// The type of the custom manager. - /// The . - public OpenIddictCoreBuilder ReplaceAuthorizationManager(Type type) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + Services.Replace(ServiceDescriptor.Scoped(type, type)); + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>), type)); + } - var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictAuthorizationManager<>)); - if (root is null) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + else + { + object ResolveManager(IServiceProvider provider) + => provider.GetRequiredService(typeof(OpenIddictApplicationManager<>) + .MakeGenericType(root.GenericTypeArguments[0])); - // Note: managers can be either open generics (e.g OpenIddictAuthorizationManager<>) - // or closed generics (e.g OpenIddictAuthorizationManager). - if (type.IsGenericTypeDefinition) - { - if (type.GetGenericArguments() is not { Length: 1 }) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager)); + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } - Services.Replace(ServiceDescriptor.Scoped(type, type)); - Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>), type)); - } + return this; + } - else - { - object ResolveManager(IServiceProvider provider) - => provider.GetRequiredService(typeof(OpenIddictAuthorizationManager<>) - .MakeGenericType(root.GenericTypeArguments[0])); + /// + /// Replaces the default application store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder ReplaceApplicationStoreResolver(ServiceLifetime lifetime = ServiceLifetime.Scoped) + where TResolver : IOpenIddictApplicationStoreResolver + => ReplaceApplicationStoreResolver(typeof(TResolver), lifetime); - Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager)); - Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>) - .MakeGenericType(root.GenericTypeArguments[0]), type)); - } + /// + /// Replaces the default application store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder ReplaceApplicationStoreResolver( + Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } - return this; - } - - /// - /// Replaces the default authorization store resolver by a custom implementation. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver(ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TResolver : IOpenIddictAuthorizationStoreResolver - => ReplaceAuthorizationStoreResolver(typeof(TResolver), lifetime); - - /// - /// Replaces the default authorization store resolver by a custom implementation. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver( - Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + if (!typeof(IOpenIddictApplicationStoreResolver).IsAssignableFrom(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - if (!typeof(IOpenIddictAuthorizationStoreResolver).IsAssignableFrom(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStoreResolver), type, lifetime)); - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStoreResolver), type, lifetime)); - - return this; - } - - /// - /// Replace the default scope manager by a custom manager - /// derived from . - /// Note: when using this overload, the scope manager - /// must be either a non-generic or closed generic service. - /// - /// The type of the custom manager. - /// The . - public OpenIddictCoreBuilder ReplaceScopeManager() - where TManager : class - => ReplaceScopeManager(typeof(TManager)); - - /// - /// Replace the default scope manager by a custom manager - /// derived from . - /// Note: when using this overload, the scope manager can be - /// either a non-generic, a closed or an open generic service. - /// - /// The type of the custom manager. - /// The . - public OpenIddictCoreBuilder ReplaceScopeManager(Type type) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + return this; + } + + /// + /// Replace the default authorization manager by a custom manager derived + /// from . + /// Note: when using this overload, the authorization manager + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceAuthorizationManager() + where TManager : class + => ReplaceAuthorizationManager(typeof(TManager)); + + /// + /// Replace the default authorization manager by a custom manager derived + /// from . + /// Note: when using this overload, the authorization manager can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceAuthorizationManager(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictAuthorizationManager<>)); + if (root is null) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictScopeManager<>)); - if (root is null) + // Note: managers can be either open generics (e.g OpenIddictAuthorizationManager<>) + // or closed generics (e.g OpenIddictAuthorizationManager). + if (type.IsGenericTypeDefinition) + { + if (type.GetGenericArguments() is not { Length: 1 }) { throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); } - // Note: managers can be either open generics (e.g OpenIddictScopeManager<>) - // or closed generics (e.g OpenIddictScopeManager). - if (type.IsGenericTypeDefinition) - { - if (type.GetGenericArguments() is not { Length: 1 }) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } - - Services.Replace(ServiceDescriptor.Scoped(type, type)); - Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>), type)); - } + Services.Replace(ServiceDescriptor.Scoped(type, type)); + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>), type)); + } - else - { - object ResolveManager(IServiceProvider provider) - => provider.GetRequiredService(typeof(OpenIddictScopeManager<>) - .MakeGenericType(root.GenericTypeArguments[0])); + else + { + object ResolveManager(IServiceProvider provider) + => provider.GetRequiredService(typeof(OpenIddictAuthorizationManager<>) + .MakeGenericType(root.GenericTypeArguments[0])); - Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager)); - Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>) - .MakeGenericType(root.GenericTypeArguments[0]), type)); - } + Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager)); + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } - return this; - } - - /// - /// Replaces the default scope store resolver by a custom implementation. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder ReplaceScopeStoreResolver(ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TResolver : IOpenIddictScopeStoreResolver - => ReplaceScopeStoreResolver(typeof(TResolver), lifetime); - - /// - /// Replaces the default scope store resolver by a custom implementation. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder ReplaceScopeStoreResolver( - Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + return this; + } - if (!typeof(IOpenIddictScopeStoreResolver).IsAssignableFrom(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + /// + /// Replaces the default authorization store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver(ServiceLifetime lifetime = ServiceLifetime.Scoped) + where TResolver : IOpenIddictAuthorizationStoreResolver + => ReplaceAuthorizationStoreResolver(typeof(TResolver), lifetime); - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStoreResolver), type, lifetime)); - - return this; - } - - /// - /// Replace the default token manager by a custom manager - /// derived from . - /// Note: when using this overload, the token manager - /// must be either a non-generic or closed generic service. - /// - /// The type of the custom manager. - /// The . - public OpenIddictCoreBuilder ReplaceTokenManager() - where TManager : class - => ReplaceTokenManager(typeof(TManager)); - - /// - /// Replace the default token manager by a custom manager - /// derived from . - /// Note: when using this overload, the token manager can be - /// either a non-generic, a closed or an open generic service. - /// - /// The type of the custom manager. - /// The . - public OpenIddictCoreBuilder ReplaceTokenManager(Type type) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + /// + /// Replaces the default authorization store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver( + Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } - var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictTokenManager<>)); - if (root is null) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + if (!typeof(IOpenIddictAuthorizationStoreResolver).IsAssignableFrom(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - // Note: managers can be either open generics (e.g OpenIddictTokenManager<>) - // or closed generics (e.g OpenIddictTokenManager). - if (type.IsGenericTypeDefinition) - { - if (type.GetGenericArguments() is not { Length: 1 }) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStoreResolver), type, lifetime)); - Services.Replace(ServiceDescriptor.Scoped(type, type)); - Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>), type)); - } + return this; + } - else - { - object ResolveManager(IServiceProvider provider) - => provider.GetRequiredService(typeof(OpenIddictTokenManager<>) - .MakeGenericType(root.GenericTypeArguments[0])); + /// + /// Replace the default scope manager by a custom manager + /// derived from . + /// Note: when using this overload, the scope manager + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceScopeManager() + where TManager : class + => ReplaceScopeManager(typeof(TManager)); - Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager)); - Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>) - .MakeGenericType(root.GenericTypeArguments[0]), type)); - } + /// + /// Replace the default scope manager by a custom manager + /// derived from . + /// Note: when using this overload, the scope manager can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceScopeManager(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } - return this; - } - - /// - /// Replaces the default token store resolver by a custom implementation. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder ReplaceTokenStoreResolver(ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TResolver : IOpenIddictTokenStoreResolver - => ReplaceTokenStoreResolver(typeof(TResolver), lifetime); - - /// - /// Replaces the default token store resolver by a custom implementation. - /// - /// The type of the custom store. - /// The lifetime of the registered service. - /// The . - public OpenIddictCoreBuilder ReplaceTokenStoreResolver( - Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictScopeManager<>)); + if (root is null) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - if (!typeof(IOpenIddictTokenStoreResolver).IsAssignableFrom(type)) + // Note: managers can be either open generics (e.g OpenIddictScopeManager<>) + // or closed generics (e.g OpenIddictScopeManager). + if (type.IsGenericTypeDefinition) + { + if (type.GetGenericArguments() is not { Length: 1 }) { throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); } - Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStoreResolver), type, lifetime)); - - return this; - } - - /// - /// Disables additional filtering so that the OpenIddict managers don't execute a second check - /// to ensure the results returned by the stores exactly match the specified query filters, - /// casing included. Additional filtering shouldn't be disabled except when the underlying - /// stores are guaranteed to execute case-sensitive filtering at the database level. - /// Disabling this feature MAY result in security vulnerabilities in the other cases. - /// - /// The . - public OpenIddictCoreBuilder DisableAdditionalFiltering() - => Configure(options => options.DisableAdditionalFiltering = true); - - /// - /// Disables the scoped entity caching applied by the OpenIddict managers. - /// Disabling entity caching may have a noticeable impact on the performance - /// of your application and result in multiple queries being sent by the stores. - /// - /// The . - public OpenIddictCoreBuilder DisableEntityCaching() - => Configure(options => options.DisableEntityCaching = true); - - /// - /// Configures OpenIddict to use the specified entity as the default application entity. - /// - /// The . - public OpenIddictCoreBuilder SetDefaultApplicationEntity() where TApplication : class - => SetDefaultApplicationEntity(typeof(TApplication)); - - /// - /// Configures OpenIddict to use the specified entity as the default application entity. - /// - /// The application entity type. - /// The . - public OpenIddictCoreBuilder SetDefaultApplicationEntity(Type type) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + Services.Replace(ServiceDescriptor.Scoped(type, type)); + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>), type)); + } - if (type.IsValueType) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + else + { + object ResolveManager(IServiceProvider provider) + => provider.GetRequiredService(typeof(OpenIddictScopeManager<>) + .MakeGenericType(root.GenericTypeArguments[0])); - return Configure(options => options.DefaultApplicationType = type); + Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager)); + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); } - /// - /// Configures OpenIddict to use the specified entity as the default authorization entity. - /// - /// The . - public OpenIddictCoreBuilder SetDefaultAuthorizationEntity() where TAuthorization : class - => SetDefaultAuthorizationEntity(typeof(TAuthorization)); + return this; + } - /// - /// Configures OpenIddict to use the specified entity as the default authorization entity. - /// - /// The authorization entity type. - /// The . - public OpenIddictCoreBuilder SetDefaultAuthorizationEntity(Type type) + /// + /// Replaces the default scope store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder ReplaceScopeStoreResolver(ServiceLifetime lifetime = ServiceLifetime.Scoped) + where TResolver : IOpenIddictScopeStoreResolver + => ReplaceScopeStoreResolver(typeof(TResolver), lifetime); + + /// + /// Replaces the default scope store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder ReplaceScopeStoreResolver( + Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + if (type is null) { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + throw new ArgumentNullException(nameof(type)); + } + + if (!typeof(IOpenIddictScopeStoreResolver).IsAssignableFrom(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - if (type.IsValueType) + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStoreResolver), type, lifetime)); + + return this; + } + + /// + /// Replace the default token manager by a custom manager + /// derived from . + /// Note: when using this overload, the token manager + /// must be either a non-generic or closed generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceTokenManager() + where TManager : class + => ReplaceTokenManager(typeof(TManager)); + + /// + /// Replace the default token manager by a custom manager + /// derived from . + /// Note: when using this overload, the token manager can be + /// either a non-generic, a closed or an open generic service. + /// + /// The type of the custom manager. + /// The . + public OpenIddictCoreBuilder ReplaceTokenManager(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictTokenManager<>)); + if (root is null) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } + + // Note: managers can be either open generics (e.g OpenIddictTokenManager<>) + // or closed generics (e.g OpenIddictTokenManager). + if (type.IsGenericTypeDefinition) + { + if (type.GetGenericArguments() is not { Length: 1 }) { throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); } - return Configure(options => options.DefaultAuthorizationType = type); + Services.Replace(ServiceDescriptor.Scoped(type, type)); + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>), type)); } - /// - /// Configures OpenIddict to use the specified entity as the default scope entity. - /// - /// The . - public OpenIddictCoreBuilder SetDefaultScopeEntity() where TScope : class - => SetDefaultScopeEntity(typeof(TScope)); + else + { + object ResolveManager(IServiceProvider provider) + => provider.GetRequiredService(typeof(OpenIddictTokenManager<>) + .MakeGenericType(root.GenericTypeArguments[0])); - /// - /// Configures OpenIddict to use the specified entity as the default scope entity. - /// - /// The scope entity type. - /// The . - public OpenIddictCoreBuilder SetDefaultScopeEntity(Type type) + Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager)); + Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>) + .MakeGenericType(root.GenericTypeArguments[0]), type)); + } + + return this; + } + + /// + /// Replaces the default token store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder ReplaceTokenStoreResolver(ServiceLifetime lifetime = ServiceLifetime.Scoped) + where TResolver : IOpenIddictTokenStoreResolver + => ReplaceTokenStoreResolver(typeof(TResolver), lifetime); + + /// + /// Replaces the default token store resolver by a custom implementation. + /// + /// The type of the custom store. + /// The lifetime of the registered service. + /// The . + public OpenIddictCoreBuilder ReplaceTokenStoreResolver( + Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) + { + if (type is null) { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + throw new ArgumentNullException(nameof(type)); + } - if (type.IsValueType) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + if (!typeof(IOpenIddictTokenStoreResolver).IsAssignableFrom(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } + + Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStoreResolver), type, lifetime)); + + return this; + } + + /// + /// Disables additional filtering so that the OpenIddict managers don't execute a second check + /// to ensure the results returned by the stores exactly match the specified query filters, + /// casing included. Additional filtering shouldn't be disabled except when the underlying + /// stores are guaranteed to execute case-sensitive filtering at the database level. + /// Disabling this feature MAY result in security vulnerabilities in the other cases. + /// + /// The . + public OpenIddictCoreBuilder DisableAdditionalFiltering() + => Configure(options => options.DisableAdditionalFiltering = true); - return Configure(options => options.DefaultScopeType = type); + /// + /// Disables the scoped entity caching applied by the OpenIddict managers. + /// Disabling entity caching may have a noticeable impact on the performance + /// of your application and result in multiple queries being sent by the stores. + /// + /// The . + public OpenIddictCoreBuilder DisableEntityCaching() + => Configure(options => options.DisableEntityCaching = true); + + /// + /// Configures OpenIddict to use the specified entity as the default application entity. + /// + /// The . + public OpenIddictCoreBuilder SetDefaultApplicationEntity() where TApplication : class + => SetDefaultApplicationEntity(typeof(TApplication)); + + /// + /// Configures OpenIddict to use the specified entity as the default application entity. + /// + /// The application entity type. + /// The . + public OpenIddictCoreBuilder SetDefaultApplicationEntity(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); } - /// - /// Configures OpenIddict to use the specified entity as the default token entity. - /// - /// The . - public OpenIddictCoreBuilder SetDefaultTokenEntity() where TToken : class - => SetDefaultTokenEntity(typeof(TToken)); + if (type.IsValueType) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } + + return Configure(options => options.DefaultApplicationType = type); + } + + /// + /// Configures OpenIddict to use the specified entity as the default authorization entity. + /// + /// The . + public OpenIddictCoreBuilder SetDefaultAuthorizationEntity() where TAuthorization : class + => SetDefaultAuthorizationEntity(typeof(TAuthorization)); + + /// + /// Configures OpenIddict to use the specified entity as the default authorization entity. + /// + /// The authorization entity type. + /// The . + public OpenIddictCoreBuilder SetDefaultAuthorizationEntity(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } - /// - /// Configures OpenIddict to use the specified entity as the default token entity. - /// - /// The token entity type. - /// The . - public OpenIddictCoreBuilder SetDefaultTokenEntity(Type type) + if (type.IsValueType) { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - if (type.IsValueType) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + return Configure(options => options.DefaultAuthorizationType = type); + } - return Configure(options => options.DefaultTokenType = type); + /// + /// Configures OpenIddict to use the specified entity as the default scope entity. + /// + /// The . + public OpenIddictCoreBuilder SetDefaultScopeEntity() where TScope : class + => SetDefaultScopeEntity(typeof(TScope)); + + /// + /// Configures OpenIddict to use the specified entity as the default scope entity. + /// + /// The scope entity type. + /// The . + public OpenIddictCoreBuilder SetDefaultScopeEntity(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); } - /// - /// Configures OpenIddict to use the specified entity cache limit, - /// after which the internal cache is automatically compacted. - /// - /// The cache limit, in number of entries. - /// The . - public OpenIddictCoreBuilder SetEntityCacheLimit(int limit) + if (type.IsValueType) { - if (limit < 10) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0233), nameof(limit)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - return Configure(options => options.EntityCacheLimit = limit); + return Configure(options => options.DefaultScopeType = type); + } + + /// + /// Configures OpenIddict to use the specified entity as the default token entity. + /// + /// The . + public OpenIddictCoreBuilder SetDefaultTokenEntity() where TToken : class + => SetDefaultTokenEntity(typeof(TToken)); + + /// + /// Configures OpenIddict to use the specified entity as the default token entity. + /// + /// The token entity type. + /// The . + public OpenIddictCoreBuilder SetDefaultTokenEntity(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (type.IsValueType) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + return Configure(options => options.DefaultTokenType = type); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + /// Configures OpenIddict to use the specified entity cache limit, + /// after which the internal cache is automatically compacted. + /// + /// The cache limit, in number of entries. + /// The . + public OpenIddictCoreBuilder SetEntityCacheLimit(int limit) + { + if (limit < 10) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0233), nameof(limit)); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); + return Configure(options => options.EntityCacheLimit = limit); } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Core/OpenIddictCoreExtensions.cs b/src/OpenIddict.Core/OpenIddictCoreExtensions.cs index a2781541..e8e9a12f 100644 --- a/src/OpenIddict.Core/OpenIddictCoreExtensions.cs +++ b/src/OpenIddict.Core/OpenIddictCoreExtensions.cs @@ -10,120 +10,119 @@ using OpenIddict.Abstractions; using OpenIddict.Core; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection -{ - using Microsoft.Extensions.Options; +namespace Microsoft.Extensions.DependencyInjection; + +using Microsoft.Extensions.Options; +/// +/// Exposes extensions allowing to register the OpenIddict core services. +/// +public static class OpenIddictCoreExtensions +{ /// - /// Exposes extensions allowing to register the OpenIddict core services. + /// Registers the OpenIddict core services in the DI container. /// - public static class OpenIddictCoreExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictCoreBuilder AddCore(this OpenIddictBuilder builder) { - /// - /// Registers the OpenIddict core services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictCoreBuilder AddCore(this OpenIddictBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddLogging(); - builder.Services.AddMemoryCache(); - builder.Services.AddOptions(); + builder.Services.AddLogging(); + builder.Services.AddMemoryCache(); + builder.Services.AddOptions(); - builder.Services.TryAddScoped(typeof(OpenIddictApplicationManager<>)); - builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationManager<>)); - builder.Services.TryAddScoped(typeof(OpenIddictScopeManager<>)); - builder.Services.TryAddScoped(typeof(OpenIddictTokenManager<>)); + builder.Services.TryAddScoped(typeof(OpenIddictApplicationManager<>)); + builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationManager<>)); + builder.Services.TryAddScoped(typeof(OpenIddictScopeManager<>)); + builder.Services.TryAddScoped(typeof(OpenIddictTokenManager<>)); - builder.Services.TryAddScoped(typeof(IOpenIddictApplicationCache<>), typeof(OpenIddictApplicationCache<>)); - builder.Services.TryAddScoped(typeof(IOpenIddictAuthorizationCache<>), typeof(OpenIddictAuthorizationCache<>)); - builder.Services.TryAddScoped(typeof(IOpenIddictScopeCache<>), typeof(OpenIddictScopeCache<>)); - builder.Services.TryAddScoped(typeof(IOpenIddictTokenCache<>), typeof(OpenIddictTokenCache<>)); + builder.Services.TryAddScoped(typeof(IOpenIddictApplicationCache<>), typeof(OpenIddictApplicationCache<>)); + builder.Services.TryAddScoped(typeof(IOpenIddictAuthorizationCache<>), typeof(OpenIddictAuthorizationCache<>)); + builder.Services.TryAddScoped(typeof(IOpenIddictScopeCache<>), typeof(OpenIddictScopeCache<>)); + builder.Services.TryAddScoped(typeof(IOpenIddictTokenCache<>), typeof(OpenIddictTokenCache<>)); - builder.Services.TryAddScoped(); - builder.Services.TryAddScoped(); - builder.Services.TryAddScoped(); - builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); - builder.Services.TryAddScoped(provider => + builder.Services.TryAddScoped(provider => + { + var options = provider.GetRequiredService>().CurrentValue; + if (options.DefaultApplicationType is null) { - var options = provider.GetRequiredService>().CurrentValue; - if (options.DefaultApplicationType is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0273)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0273)); + } - return (IOpenIddictApplicationManager) provider.GetRequiredService( - typeof(OpenIddictApplicationManager<>).MakeGenericType(options.DefaultApplicationType)); - }); + return (IOpenIddictApplicationManager) provider.GetRequiredService( + typeof(OpenIddictApplicationManager<>).MakeGenericType(options.DefaultApplicationType)); + }); - builder.Services.TryAddScoped(provider => + builder.Services.TryAddScoped(provider => + { + var options = provider.GetRequiredService>().CurrentValue; + if (options.DefaultAuthorizationType is null) { - var options = provider.GetRequiredService>().CurrentValue; - if (options.DefaultAuthorizationType is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0274)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0274)); + } - return (IOpenIddictAuthorizationManager) provider.GetRequiredService( - typeof(OpenIddictAuthorizationManager<>).MakeGenericType(options.DefaultAuthorizationType)); - }); + return (IOpenIddictAuthorizationManager) provider.GetRequiredService( + typeof(OpenIddictAuthorizationManager<>).MakeGenericType(options.DefaultAuthorizationType)); + }); - builder.Services.TryAddScoped(provider => + builder.Services.TryAddScoped(provider => + { + var options = provider.GetRequiredService>().CurrentValue; + if (options.DefaultScopeType is null) { - var options = provider.GetRequiredService>().CurrentValue; - if (options.DefaultScopeType is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0275)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0275)); + } - return (IOpenIddictScopeManager) provider.GetRequiredService( - typeof(OpenIddictScopeManager<>).MakeGenericType(options.DefaultScopeType)); - }); + return (IOpenIddictScopeManager) provider.GetRequiredService( + typeof(OpenIddictScopeManager<>).MakeGenericType(options.DefaultScopeType)); + }); - builder.Services.TryAddScoped(provider => + builder.Services.TryAddScoped(provider => + { + var options = provider.GetRequiredService>().CurrentValue; + if (options.DefaultTokenType is null) { - var options = provider.GetRequiredService>().CurrentValue; - if (options.DefaultTokenType is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0276)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0276)); + } - return (IOpenIddictTokenManager) provider.GetRequiredService( - typeof(OpenIddictTokenManager<>).MakeGenericType(options.DefaultTokenType)); - }); + return (IOpenIddictTokenManager) provider.GetRequiredService( + typeof(OpenIddictTokenManager<>).MakeGenericType(options.DefaultTokenType)); + }); - return new OpenIddictCoreBuilder(builder.Services); - } + return new OpenIddictCoreBuilder(builder.Services); + } - /// - /// Registers the OpenIddict core services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the core services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictBuilder AddCore(this OpenIddictBuilder builder, Action configuration) + /// + /// Registers the OpenIddict core services in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the core services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictBuilder AddCore(this OpenIddictBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.AddCore()); + configuration(builder.AddCore()); - return builder; - } + return builder; } -} \ No newline at end of file +} diff --git a/src/OpenIddict.Core/OpenIddictCoreOptions.cs b/src/OpenIddict.Core/OpenIddictCoreOptions.cs index cd76b419..e11f8075 100644 --- a/src/OpenIddict.Core/OpenIddictCoreOptions.cs +++ b/src/OpenIddict.Core/OpenIddictCoreOptions.cs @@ -6,60 +6,59 @@ using System; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Provides various settings needed to configure the OpenIddict core services. +/// +public class OpenIddictCoreOptions { /// - /// Provides various settings needed to configure the OpenIddict core services. + /// Gets or sets the type corresponding to the default Application entity, + /// used by the non-generic application manager and the server/validation services. /// - public class OpenIddictCoreOptions - { - /// - /// Gets or sets the type corresponding to the default Application entity, - /// used by the non-generic application manager and the server/validation services. - /// - public Type? DefaultApplicationType { get; set; } + public Type? DefaultApplicationType { get; set; } - /// - /// Gets or sets the type corresponding to the default Authorization entity, - /// used by the non-generic authorization manager and the server/validation services. - /// - public Type? DefaultAuthorizationType { get; set; } + /// + /// Gets or sets the type corresponding to the default Authorization entity, + /// used by the non-generic authorization manager and the server/validation services. + /// + public Type? DefaultAuthorizationType { get; set; } - /// - /// Gets or sets the type corresponding to the default Scope entity, - /// used by the non-generic scope manager and the server/validation services. - /// - public Type? DefaultScopeType { get; set; } + /// + /// Gets or sets the type corresponding to the default Scope entity, + /// used by the non-generic scope manager and the server/validation services. + /// + public Type? DefaultScopeType { get; set; } - /// - /// Gets or sets the type corresponding to the default Token entity, - /// used by the non-generic token manager and the server/validation services. - /// - public Type? DefaultTokenType { get; set; } + /// + /// Gets or sets the type corresponding to the default Token entity, + /// used by the non-generic token manager and the server/validation services. + /// + public Type? DefaultTokenType { get; set; } - /// - /// Gets or sets a boolean indicating whether additional filtering should be disabled, - /// so that the OpenIddict managers don't execute a second check to ensure the results - /// returned by the stores exactly match the specified query filters, casing included. - /// This property SHOULD NOT be set to true except when the underlying stores - /// are guaranteed to execute case-sensitive filtering at the database level. - /// Disabling this feature MAY result in security vulnerabilities in the other cases. - /// - public bool DisableAdditionalFiltering { get; set; } + /// + /// Gets or sets a boolean indicating whether additional filtering should be disabled, + /// so that the OpenIddict managers don't execute a second check to ensure the results + /// returned by the stores exactly match the specified query filters, casing included. + /// This property SHOULD NOT be set to true except when the underlying stores + /// are guaranteed to execute case-sensitive filtering at the database level. + /// Disabling this feature MAY result in security vulnerabilities in the other cases. + /// + public bool DisableAdditionalFiltering { get; set; } - /// - /// Gets or sets a boolean indicating whether entity caching should be disabled. - /// Disabling entity caching may have a noticeable impact on the performance - /// of your application and result in multiple queries being sent by the stores. - /// - public bool DisableEntityCaching { get; set; } + /// + /// Gets or sets a boolean indicating whether entity caching should be disabled. + /// Disabling entity caching may have a noticeable impact on the performance + /// of your application and result in multiple queries being sent by the stores. + /// + public bool DisableEntityCaching { get; set; } - /// - /// Gets or sets the maximum number of cached entries allowed. When the threshold - /// is reached, the cache is automatically compacted to ensure it doesn't grow - /// abnormally and doesn't cause a memory starvation or out-of-memory exceptions. - /// This property is not used when is true. - /// - public int EntityCacheLimit { get; set; } = 250; - } -} \ No newline at end of file + /// + /// Gets or sets the maximum number of cached entries allowed. When the threshold + /// is reached, the cache is automatically compacted to ensure it doesn't grow + /// abnormally and doesn't cause a memory starvation or out-of-memory exceptions. + /// This property is not used when is true. + /// + public int EntityCacheLimit { get; set; } = 250; +} diff --git a/src/OpenIddict.Core/Resolvers/OpenIddictApplicationStoreResolver.cs b/src/OpenIddict.Core/Resolvers/OpenIddictApplicationStoreResolver.cs index 9a9e9345..e3ccbb4f 100644 --- a/src/OpenIddict.Core/Resolvers/OpenIddictApplicationStoreResolver.cs +++ b/src/OpenIddict.Core/Resolvers/OpenIddictApplicationStoreResolver.cs @@ -3,26 +3,25 @@ using Microsoft.Extensions.DependencyInjection; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Exposes a method allowing to resolve an application store. +/// +public class OpenIddictApplicationStoreResolver : IOpenIddictApplicationStoreResolver { - /// - /// Exposes a method allowing to resolve an application store. - /// - public class OpenIddictApplicationStoreResolver : IOpenIddictApplicationStoreResolver - { - private readonly IServiceProvider _provider; + private readonly IServiceProvider _provider; - public OpenIddictApplicationStoreResolver(IServiceProvider provider) - => _provider = provider; + public OpenIddictApplicationStoreResolver(IServiceProvider provider) + => _provider = provider; - /// - /// Returns an application store compatible with the specified application type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Application entity. - /// An . - public IOpenIddictApplicationStore Get() where TApplication : class - => _provider.GetService>() ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0228)); - } + /// + /// Returns an application store compatible with the specified application type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Application entity. + /// An . + public IOpenIddictApplicationStore Get() where TApplication : class + => _provider.GetService>() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0228)); } diff --git a/src/OpenIddict.Core/Resolvers/OpenIddictAuthorizationStoreResolver.cs b/src/OpenIddict.Core/Resolvers/OpenIddictAuthorizationStoreResolver.cs index 4122b628..ca51833e 100644 --- a/src/OpenIddict.Core/Resolvers/OpenIddictAuthorizationStoreResolver.cs +++ b/src/OpenIddict.Core/Resolvers/OpenIddictAuthorizationStoreResolver.cs @@ -3,26 +3,25 @@ using Microsoft.Extensions.DependencyInjection; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Exposes a method allowing to resolve an authorization store. +/// +public class OpenIddictAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver { - /// - /// Exposes a method allowing to resolve an authorization store. - /// - public class OpenIddictAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver - { - private readonly IServiceProvider _provider; + private readonly IServiceProvider _provider; - public OpenIddictAuthorizationStoreResolver(IServiceProvider provider) - => _provider = provider; + public OpenIddictAuthorizationStoreResolver(IServiceProvider provider) + => _provider = provider; - /// - /// Returns an authorization store compatible with the specified authorization type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Authorization entity. - /// An . - public IOpenIddictAuthorizationStore Get() where TAuthorization : class - => _provider.GetService>() ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0229)); - } + /// + /// Returns an authorization store compatible with the specified authorization type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Authorization entity. + /// An . + public IOpenIddictAuthorizationStore Get() where TAuthorization : class + => _provider.GetService>() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0229)); } diff --git a/src/OpenIddict.Core/Resolvers/OpenIddictScopeStoreResolver.cs b/src/OpenIddict.Core/Resolvers/OpenIddictScopeStoreResolver.cs index 1bbd9a5d..9cf616a2 100644 --- a/src/OpenIddict.Core/Resolvers/OpenIddictScopeStoreResolver.cs +++ b/src/OpenIddict.Core/Resolvers/OpenIddictScopeStoreResolver.cs @@ -3,26 +3,25 @@ using Microsoft.Extensions.DependencyInjection; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Exposes a method allowing to resolve a scope store. +/// +public class OpenIddictScopeStoreResolver : IOpenIddictScopeStoreResolver { - /// - /// Exposes a method allowing to resolve a scope store. - /// - public class OpenIddictScopeStoreResolver : IOpenIddictScopeStoreResolver - { - private readonly IServiceProvider _provider; + private readonly IServiceProvider _provider; - public OpenIddictScopeStoreResolver(IServiceProvider provider) - => _provider = provider; + public OpenIddictScopeStoreResolver(IServiceProvider provider) + => _provider = provider; - /// - /// Returns a scope store compatible with the specified scope type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Scope entity. - /// An . - public IOpenIddictScopeStore Get() where TScope : class - => _provider.GetService>() ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0230)); - } + /// + /// Returns a scope store compatible with the specified scope type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Scope entity. + /// An . + public IOpenIddictScopeStore Get() where TScope : class + => _provider.GetService>() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0230)); } diff --git a/src/OpenIddict.Core/Resolvers/OpenIddictTokenStoreResolver.cs b/src/OpenIddict.Core/Resolvers/OpenIddictTokenStoreResolver.cs index 33fbdcd2..6547cd8a 100644 --- a/src/OpenIddict.Core/Resolvers/OpenIddictTokenStoreResolver.cs +++ b/src/OpenIddict.Core/Resolvers/OpenIddictTokenStoreResolver.cs @@ -3,26 +3,25 @@ using Microsoft.Extensions.DependencyInjection; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core +namespace OpenIddict.Core; + +/// +/// Exposes a method allowing to resolve a token store. +/// +public class OpenIddictTokenStoreResolver : IOpenIddictTokenStoreResolver { - /// - /// Exposes a method allowing to resolve a token store. - /// - public class OpenIddictTokenStoreResolver : IOpenIddictTokenStoreResolver - { - private readonly IServiceProvider _provider; + private readonly IServiceProvider _provider; - public OpenIddictTokenStoreResolver(IServiceProvider provider) - => _provider = provider; + public OpenIddictTokenStoreResolver(IServiceProvider provider) + => _provider = provider; - /// - /// Returns a token store compatible with the specified token type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Token entity. - /// An . - public IOpenIddictTokenStore Get() where TToken : class - => _provider.GetService>() ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0231)); - } + /// + /// Returns a token store compatible with the specified token type or throws an + /// if no store can be built using the specified type. + /// + /// The type of the Token entity. + /// An . + public IOpenIddictTokenStore Get() where TToken : class + => _provider.GetService>() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0231)); } diff --git a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs index c084f271..1c274973 100644 --- a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs +++ b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs @@ -8,111 +8,110 @@ using System; using System.Collections.Generic; using System.Diagnostics; -namespace OpenIddict.EntityFramework.Models +namespace OpenIddict.EntityFramework.Models; + +/// +/// Represents an OpenIddict application. +/// +public class OpenIddictEntityFrameworkApplication : OpenIddictEntityFrameworkApplication { - /// - /// Represents an OpenIddict application. - /// - public class OpenIddictEntityFrameworkApplication : OpenIddictEntityFrameworkApplication + public OpenIddictEntityFrameworkApplication() { - public OpenIddictEntityFrameworkApplication() - { - // Generate a new string identifier. - Id = Guid.NewGuid().ToString(); - } + // Generate a new string identifier. + Id = Guid.NewGuid().ToString(); } +} + +/// +/// Represents an OpenIddict application. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; ClientId = {ClientId,nq} ; Type = {Type,nq}")] +public class OpenIddictEntityFrameworkApplication + where TKey : notnull, IEquatable + where TAuthorization : class + where TToken : class +{ + /// + /// Gets the list of the authorizations associated with this application. + /// + public virtual ICollection Authorizations { get; } = new HashSet(); /// - /// Represents an OpenIddict application. + /// Gets or sets the client identifier associated with the current application. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; ClientId = {ClientId,nq} ; Type = {Type,nq}")] - public class OpenIddictEntityFrameworkApplication - where TKey : notnull, IEquatable - where TAuthorization : class - where TToken : class - { - /// - /// Gets the list of the authorizations associated with this application. - /// - public virtual ICollection Authorizations { get; } = new HashSet(); - - /// - /// Gets or sets the client identifier associated with the current application. - /// - public virtual string? ClientId { get; set; } - - /// - /// Gets or sets the client secret associated with the current application. - /// Note: depending on the application manager used to create this instance, - /// this property may be hashed or encrypted for security reasons. - /// - public virtual string? ClientSecret { get; set; } - - /// - /// Gets or sets the concurrency token. - /// - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - - /// - /// Gets or sets the consent type associated with the current application. - /// - public virtual string? ConsentType { get; set; } - - /// - /// Gets or sets the display name associated with the current application. - /// - public virtual string? DisplayName { get; set; } - - /// - /// Gets or sets the localized display names - /// associated with the current application, - /// serialized as a JSON object. - /// - public virtual string? DisplayNames { get; set; } - - /// - /// Gets or sets the unique identifier associated with the current application. - /// - public virtual TKey? Id { get; set; } - - /// - /// Gets or sets the permissions associated with the - /// current application, serialized as a JSON array. - /// - public virtual string? Permissions { get; set; } - - /// - /// Gets or sets the logout callback URLs associated with - /// the current application, serialized as a JSON array. - /// - public virtual string? PostLogoutRedirectUris { get; set; } - - /// - /// Gets or sets the additional properties serialized as a JSON object, - /// or null if no bag was associated with the current application. - /// - public virtual string? Properties { get; set; } - - /// - /// Gets or sets the callback URLs associated with the - /// current application, serialized as a JSON array. - /// - public virtual string? RedirectUris { get; set; } - - /// - /// Gets or sets the requirements associated with the - /// current application, serialized as a JSON array. - /// - public virtual string? Requirements { get; set; } - - /// - /// Gets the list of the tokens associated with this application. - /// - public virtual ICollection Tokens { get; } = new HashSet(); - - /// - /// Gets or sets the application type associated with the current application. - /// - public virtual string? Type { get; set; } - } -} \ No newline at end of file + public virtual string? ClientId { get; set; } + + /// + /// Gets or sets the client secret associated with the current application. + /// Note: depending on the application manager used to create this instance, + /// this property may be hashed or encrypted for security reasons. + /// + public virtual string? ClientSecret { get; set; } + + /// + /// Gets or sets the concurrency token. + /// + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + + /// + /// Gets or sets the consent type associated with the current application. + /// + public virtual string? ConsentType { get; set; } + + /// + /// Gets or sets the display name associated with the current application. + /// + public virtual string? DisplayName { get; set; } + + /// + /// Gets or sets the localized display names + /// associated with the current application, + /// serialized as a JSON object. + /// + public virtual string? DisplayNames { get; set; } + + /// + /// Gets or sets the unique identifier associated with the current application. + /// + public virtual TKey? Id { get; set; } + + /// + /// Gets or sets the permissions associated with the + /// current application, serialized as a JSON array. + /// + public virtual string? Permissions { get; set; } + + /// + /// Gets or sets the logout callback URLs associated with + /// the current application, serialized as a JSON array. + /// + public virtual string? PostLogoutRedirectUris { get; set; } + + /// + /// Gets or sets the additional properties serialized as a JSON object, + /// or null if no bag was associated with the current application. + /// + public virtual string? Properties { get; set; } + + /// + /// Gets or sets the callback URLs associated with the + /// current application, serialized as a JSON array. + /// + public virtual string? RedirectUris { get; set; } + + /// + /// Gets or sets the requirements associated with the + /// current application, serialized as a JSON array. + /// + public virtual string? Requirements { get; set; } + + /// + /// Gets the list of the tokens associated with this application. + /// + public virtual ICollection Tokens { get; } = new HashSet(); + + /// + /// Gets or sets the application type associated with the current application. + /// + public virtual string? Type { get; set; } +} diff --git a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs index 9dccd102..db744535 100644 --- a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs +++ b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs @@ -8,79 +8,78 @@ using System; using System.Collections.Generic; using System.Diagnostics; -namespace OpenIddict.EntityFramework.Models +namespace OpenIddict.EntityFramework.Models; + +/// +/// Represents an OpenIddict authorization. +/// +public class OpenIddictEntityFrameworkAuthorization : OpenIddictEntityFrameworkAuthorization { - /// - /// Represents an OpenIddict authorization. - /// - public class OpenIddictEntityFrameworkAuthorization : OpenIddictEntityFrameworkAuthorization + public OpenIddictEntityFrameworkAuthorization() { - public OpenIddictEntityFrameworkAuthorization() - { - // Generate a new string identifier. - Id = Guid.NewGuid().ToString(); - } + // Generate a new string identifier. + Id = Guid.NewGuid().ToString(); } +} +/// +/// Represents an OpenIddict authorization. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] +public class OpenIddictEntityFrameworkAuthorization + where TKey : notnull, IEquatable + where TApplication : class + where TToken : class +{ /// - /// Represents an OpenIddict authorization. + /// Gets or sets the application associated with the current authorization. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] - public class OpenIddictEntityFrameworkAuthorization - where TKey : notnull, IEquatable - where TApplication : class - where TToken : class - { - /// - /// Gets or sets the application associated with the current authorization. - /// - public virtual TApplication? Application { get; set; } + public virtual TApplication? Application { get; set; } - /// - /// Gets or sets the concurrency token. - /// - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + /// + /// Gets or sets the concurrency token. + /// + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - /// - /// Gets or sets the UTC creation date of the current authorization. - /// - public virtual DateTime? CreationDate { get; set; } + /// + /// Gets or sets the UTC creation date of the current authorization. + /// + public virtual DateTime? CreationDate { get; set; } - /// - /// Gets or sets the unique identifier associated with the current authorization. - /// - public virtual TKey? Id { get; set; } + /// + /// Gets or sets the unique identifier associated with the current authorization. + /// + public virtual TKey? Id { get; set; } - /// - /// Gets or sets the additional properties serialized as a JSON object, - /// or null if no bag was associated with the current authorization. - /// - public virtual string? Properties { get; set; } + /// + /// Gets or sets the additional properties serialized as a JSON object, + /// or null if no bag was associated with the current authorization. + /// + public virtual string? Properties { get; set; } - /// - /// Gets or sets the scopes associated with the current - /// authorization, serialized as a JSON array. - /// - public virtual string? Scopes { get; set; } + /// + /// Gets or sets the scopes associated with the current + /// authorization, serialized as a JSON array. + /// + public virtual string? Scopes { get; set; } - /// - /// Gets or sets the status of the current authorization. - /// - public virtual string? Status { get; set; } + /// + /// Gets or sets the status of the current authorization. + /// + public virtual string? Status { get; set; } - /// - /// Gets or sets the subject associated with the current authorization. - /// - public virtual string? Subject { get; set; } + /// + /// Gets or sets the subject associated with the current authorization. + /// + public virtual string? Subject { get; set; } - /// - /// Gets the list of tokens associated with the current authorization. - /// - public virtual ICollection Tokens { get; } = new HashSet(); + /// + /// Gets the list of tokens associated with the current authorization. + /// + public virtual ICollection Tokens { get; } = new HashSet(); - /// - /// Gets or sets the type of the current authorization. - /// - public virtual string? Type { get; set; } - } + /// + /// Gets or sets the type of the current authorization. + /// + public virtual string? Type { get; set; } } diff --git a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs index 34226a16..1f1515ca 100644 --- a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs +++ b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkScope.cs @@ -7,74 +7,73 @@ using System; using System.Diagnostics; -namespace OpenIddict.EntityFramework.Models +namespace OpenIddict.EntityFramework.Models; + +/// +/// Represents an OpenIddict scope. +/// +public class OpenIddictEntityFrameworkScope : OpenIddictEntityFrameworkScope { - /// - /// Represents an OpenIddict scope. - /// - public class OpenIddictEntityFrameworkScope : OpenIddictEntityFrameworkScope + public OpenIddictEntityFrameworkScope() { - public OpenIddictEntityFrameworkScope() - { - // Generate a new string identifier. - Id = Guid.NewGuid().ToString(); - } + // Generate a new string identifier. + Id = Guid.NewGuid().ToString(); } +} +/// +/// Represents an OpenIddict scope. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; Name = {Name,nq}")] +public class OpenIddictEntityFrameworkScope where TKey : notnull, IEquatable +{ /// - /// Represents an OpenIddict scope. + /// Gets or sets the concurrency token. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; Name = {Name,nq}")] - public class OpenIddictEntityFrameworkScope where TKey : notnull, IEquatable - { - /// - /// Gets or sets the concurrency token. - /// - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - /// - /// Gets or sets the public description associated with the current scope. - /// - public virtual string? Description { get; set; } + /// + /// Gets or sets the public description associated with the current scope. + /// + public virtual string? Description { get; set; } - /// - /// Gets or sets the localized public descriptions associated - /// with the current scope, serialized as a JSON object. - /// - public virtual string? Descriptions { get; set; } + /// + /// Gets or sets the localized public descriptions associated + /// with the current scope, serialized as a JSON object. + /// + public virtual string? Descriptions { get; set; } - /// - /// Gets or sets the display name associated with the current scope. - /// - public virtual string? DisplayName { get; set; } + /// + /// Gets or sets the display name associated with the current scope. + /// + public virtual string? DisplayName { get; set; } - /// - /// Gets or sets the localized display names - /// associated with the current application, - /// serialized as a JSON object. - /// - public virtual string? DisplayNames { get; set; } + /// + /// Gets or sets the localized display names + /// associated with the current application, + /// serialized as a JSON object. + /// + public virtual string? DisplayNames { get; set; } - /// - /// Gets or sets the unique identifier associated with the current scope. - /// - public virtual TKey? Id { get; set; } + /// + /// Gets or sets the unique identifier associated with the current scope. + /// + public virtual TKey? Id { get; set; } - /// - /// Gets or sets the unique name associated with the current scope. - /// - public virtual string? Name { get; set; } + /// + /// Gets or sets the unique name associated with the current scope. + /// + public virtual string? Name { get; set; } - /// - /// Gets or sets the additional properties serialized as a JSON object, - /// or null if no bag was associated with the current scope. - /// - public virtual string? Properties { get; set; } + /// + /// Gets or sets the additional properties serialized as a JSON object, + /// or null if no bag was associated with the current scope. + /// + public virtual string? Properties { get; set; } - /// - /// Gets or sets the resources associated with the - /// current scope, serialized as a JSON array. - /// - public virtual string? Resources { get; set; } - } + /// + /// Gets or sets the resources associated with the + /// current scope, serialized as a JSON array. + /// + public virtual string? Resources { get; set; } } diff --git a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs index 4f5c5f07..d0bced8f 100644 --- a/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs +++ b/src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs @@ -7,98 +7,97 @@ using System; using System.Diagnostics; -namespace OpenIddict.EntityFramework.Models +namespace OpenIddict.EntityFramework.Models; + +/// +/// Represents an OpenIddict token. +/// +public class OpenIddictEntityFrameworkToken : OpenIddictEntityFrameworkToken { - /// - /// Represents an OpenIddict token. - /// - public class OpenIddictEntityFrameworkToken : OpenIddictEntityFrameworkToken + public OpenIddictEntityFrameworkToken() { - public OpenIddictEntityFrameworkToken() - { - // Generate a new string identifier. - Id = Guid.NewGuid().ToString(); - } + // Generate a new string identifier. + Id = Guid.NewGuid().ToString(); } +} +/// +/// Represents an OpenIddict token. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] +public class OpenIddictEntityFrameworkToken + where TKey : notnull, IEquatable + where TApplication : class + where TAuthorization : class +{ /// - /// Represents an OpenIddict token. + /// Gets or sets the application associated with the current token. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] - public class OpenIddictEntityFrameworkToken - where TKey : notnull, IEquatable - where TApplication : class - where TAuthorization : class - { - /// - /// Gets or sets the application associated with the current token. - /// - public virtual TApplication? Application { get; set; } - - /// - /// Gets or sets the authorization associated with the current token. - /// - public virtual TAuthorization? Authorization { get; set; } - - /// - /// Gets or sets the concurrency token. - /// - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - - /// - /// Gets or sets the UTC creation date of the current token. - /// - public virtual DateTime? CreationDate { get; set; } - - /// - /// Gets or sets the UTC expiration date of the current token. - /// - public virtual DateTime? ExpirationDate { get; set; } - - /// - /// Gets or sets the unique identifier associated with the current token. - /// - public virtual TKey? Id { get; set; } - - /// - /// Gets or sets the payload of the current token, if applicable. - /// Note: this property is only used for reference tokens - /// and may be encrypted for security reasons. - /// - public virtual string? Payload { get; set; } - - /// - /// Gets or sets the additional properties serialized as a JSON object, - /// or null if no bag was associated with the current token. - /// - public virtual string? Properties { get; set; } - - /// - /// Gets or sets the UTC redemption date of the current token. - /// - public virtual DateTime? RedemptionDate { get; set; } - - /// - /// Gets or sets the reference identifier associated - /// with the current token, if applicable. - /// Note: this property is only used for reference tokens - /// and may be hashed or encrypted for security reasons. - /// - public virtual string? ReferenceId { get; set; } - - /// - /// Gets or sets the status of the current token. - /// - public virtual string? Status { get; set; } - - /// - /// Gets or sets the subject associated with the current token. - /// - public virtual string? Subject { get; set; } - - /// - /// Gets or sets the type of the current token. - /// - public virtual string? Type { get; set; } - } + public virtual TApplication? Application { get; set; } + + /// + /// Gets or sets the authorization associated with the current token. + /// + public virtual TAuthorization? Authorization { get; set; } + + /// + /// Gets or sets the concurrency token. + /// + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + + /// + /// Gets or sets the UTC creation date of the current token. + /// + public virtual DateTime? CreationDate { get; set; } + + /// + /// Gets or sets the UTC expiration date of the current token. + /// + public virtual DateTime? ExpirationDate { get; set; } + + /// + /// Gets or sets the unique identifier associated with the current token. + /// + public virtual TKey? Id { get; set; } + + /// + /// Gets or sets the payload of the current token, if applicable. + /// Note: this property is only used for reference tokens + /// and may be encrypted for security reasons. + /// + public virtual string? Payload { get; set; } + + /// + /// Gets or sets the additional properties serialized as a JSON object, + /// or null if no bag was associated with the current token. + /// + public virtual string? Properties { get; set; } + + /// + /// Gets or sets the UTC redemption date of the current token. + /// + public virtual DateTime? RedemptionDate { get; set; } + + /// + /// Gets or sets the reference identifier associated + /// with the current token, if applicable. + /// Note: this property is only used for reference tokens + /// and may be hashed or encrypted for security reasons. + /// + public virtual string? ReferenceId { get; set; } + + /// + /// Gets or sets the status of the current token. + /// + public virtual string? Status { get; set; } + + /// + /// Gets or sets the subject associated with the current token. + /// + public virtual string? Subject { get; set; } + + /// + /// Gets or sets the type of the current token. + /// + public virtual string? Type { get; set; } } diff --git a/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkApplicationConfiguration.cs b/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkApplicationConfiguration.cs index b7da257f..ab6df7d7 100644 --- a/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkApplicationConfiguration.cs +++ b/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkApplicationConfiguration.cs @@ -11,64 +11,63 @@ using System.Data.Entity.Infrastructure.Annotations; using System.Data.Entity.ModelConfiguration; using OpenIddict.EntityFramework.Models; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Defines a relational mapping for the Application entity. +/// +/// The type of the Application entity. +/// The type of the Authorization entity. +/// The type of the Token entity. +/// The type of the Key entity. +[EditorBrowsable(EditorBrowsableState.Never)] +public class OpenIddictEntityFrameworkApplicationConfiguration : EntityTypeConfiguration + where TApplication : OpenIddictEntityFrameworkApplication + where TAuthorization : OpenIddictEntityFrameworkAuthorization + where TToken : OpenIddictEntityFrameworkToken + where TKey : notnull, IEquatable { - /// - /// Defines a relational mapping for the Application entity. - /// - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Token entity. - /// The type of the Key entity. - [EditorBrowsable(EditorBrowsableState.Never)] - public class OpenIddictEntityFrameworkApplicationConfiguration : EntityTypeConfiguration - where TApplication : OpenIddictEntityFrameworkApplication - where TAuthorization : OpenIddictEntityFrameworkAuthorization - where TToken : OpenIddictEntityFrameworkToken - where TKey : notnull, IEquatable + public OpenIddictEntityFrameworkApplicationConfiguration() { - public OpenIddictEntityFrameworkApplicationConfiguration() - { - // Warning: optional foreign keys MUST NOT be added as CLR properties because - // Entity Framework would throw an exception due to the TKey generic parameter - // being non-nullable when using value types like short, int, long or Guid. + // Warning: optional foreign keys MUST NOT be added as CLR properties because + // Entity Framework would throw an exception due to the TKey generic parameter + // being non-nullable when using value types like short, int, long or Guid. - HasKey(application => application.Id); + HasKey(application => application.Id); - Property(application => application.ClientId) - .HasMaxLength(100) - .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute - { - IsUnique = true - })); + Property(application => application.ClientId) + .HasMaxLength(100) + .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute + { + IsUnique = true + })); - Property(application => application.ConcurrencyToken) - .HasMaxLength(50) - .IsConcurrencyToken(); + Property(application => application.ConcurrencyToken) + .HasMaxLength(50) + .IsConcurrencyToken(); - Property(application => application.ConsentType) - .HasMaxLength(50); + Property(application => application.ConsentType) + .HasMaxLength(50); - Property(application => application.Type) - .HasMaxLength(50); + Property(application => application.Type) + .HasMaxLength(50); - HasMany(application => application.Authorizations) - .WithOptional(authorization => authorization.Application!) - .Map(association => - { - association.MapKey(nameof(OpenIddictEntityFrameworkAuthorization.Application) + - nameof(OpenIddictEntityFrameworkApplication.Id)); - }); + HasMany(application => application.Authorizations) + .WithOptional(authorization => authorization.Application!) + .Map(association => + { + association.MapKey(nameof(OpenIddictEntityFrameworkAuthorization.Application) + + nameof(OpenIddictEntityFrameworkApplication.Id)); + }); - HasMany(application => application.Tokens) - .WithOptional(token => token.Application!) - .Map(association => - { - association.MapKey(nameof(OpenIddictEntityFrameworkToken.Application) + - nameof(OpenIddictEntityFrameworkApplication.Id)); - }); + HasMany(application => application.Tokens) + .WithOptional(token => token.Application!) + .Map(association => + { + association.MapKey(nameof(OpenIddictEntityFrameworkToken.Application) + + nameof(OpenIddictEntityFrameworkApplication.Id)); + }); - ToTable("OpenIddictApplications"); - } + ToTable("OpenIddictApplications"); } } diff --git a/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkAuthorizationConfiguration.cs b/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkAuthorizationConfiguration.cs index 7ac9b237..f5adb652 100644 --- a/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkAuthorizationConfiguration.cs +++ b/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkAuthorizationConfiguration.cs @@ -9,50 +9,49 @@ using System.ComponentModel; using System.Data.Entity.ModelConfiguration; using OpenIddict.EntityFramework.Models; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Defines a relational mapping for the Authorization entity. +/// +/// The type of the Authorization entity. +/// The type of the Application entity. +/// The type of the Token entity. +/// The type of the Key entity. +[EditorBrowsable(EditorBrowsableState.Never)] +public class OpenIddictEntityFrameworkAuthorizationConfiguration : EntityTypeConfiguration + where TAuthorization : OpenIddictEntityFrameworkAuthorization + where TApplication : OpenIddictEntityFrameworkApplication + where TToken : OpenIddictEntityFrameworkToken + where TKey : notnull, IEquatable { - /// - /// Defines a relational mapping for the Authorization entity. - /// - /// The type of the Authorization entity. - /// The type of the Application entity. - /// The type of the Token entity. - /// The type of the Key entity. - [EditorBrowsable(EditorBrowsableState.Never)] - public class OpenIddictEntityFrameworkAuthorizationConfiguration : EntityTypeConfiguration - where TAuthorization : OpenIddictEntityFrameworkAuthorization - where TApplication : OpenIddictEntityFrameworkApplication - where TToken : OpenIddictEntityFrameworkToken - where TKey : notnull, IEquatable + public OpenIddictEntityFrameworkAuthorizationConfiguration() { - public OpenIddictEntityFrameworkAuthorizationConfiguration() - { - // Warning: optional foreign keys MUST NOT be added as CLR properties because - // Entity Framework would throw an exception due to the TKey generic parameter - // being non-nullable when using value types like short, int, long or Guid. + // Warning: optional foreign keys MUST NOT be added as CLR properties because + // Entity Framework would throw an exception due to the TKey generic parameter + // being non-nullable when using value types like short, int, long or Guid. - HasKey(authorization => authorization.Id); + HasKey(authorization => authorization.Id); - Property(authorization => authorization.ConcurrencyToken) - .HasMaxLength(50) - .IsConcurrencyToken(); + Property(authorization => authorization.ConcurrencyToken) + .HasMaxLength(50) + .IsConcurrencyToken(); - Property(authorization => authorization.Status) - .HasMaxLength(50); + Property(authorization => authorization.Status) + .HasMaxLength(50); - Property(authorization => authorization.Subject) - .HasMaxLength(400); + Property(authorization => authorization.Subject) + .HasMaxLength(400); - Property(authorization => authorization.Type) - .HasMaxLength(50); + Property(authorization => authorization.Type) + .HasMaxLength(50); - HasMany(authorization => authorization.Tokens) - .WithOptional(token => token.Authorization!) - .Map(association => association.MapKey(nameof(OpenIddictEntityFrameworkToken.Authorization) + - nameof(OpenIddictEntityFrameworkAuthorization.Id))) - .WillCascadeOnDelete(); + HasMany(authorization => authorization.Tokens) + .WithOptional(token => token.Authorization!) + .Map(association => association.MapKey(nameof(OpenIddictEntityFrameworkToken.Authorization) + + nameof(OpenIddictEntityFrameworkAuthorization.Id))) + .WillCascadeOnDelete(); - ToTable("OpenIddictAuthorizations"); - } + ToTable("OpenIddictAuthorizations"); } } diff --git a/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkScopeConfiguration.cs b/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkScopeConfiguration.cs index 10fd5137..a3c318c1 100644 --- a/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkScopeConfiguration.cs +++ b/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkScopeConfiguration.cs @@ -11,38 +11,37 @@ using System.Data.Entity.Infrastructure.Annotations; using System.Data.Entity.ModelConfiguration; using OpenIddict.EntityFramework.Models; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Defines a relational mapping for the Scope entity. +/// +/// The type of the Scope entity. +/// The type of the Key entity. +[EditorBrowsable(EditorBrowsableState.Never)] +public class OpenIddictEntityFrameworkScopeConfiguration : EntityTypeConfiguration + where TScope : OpenIddictEntityFrameworkScope + where TKey : notnull, IEquatable { - /// - /// Defines a relational mapping for the Scope entity. - /// - /// The type of the Scope entity. - /// The type of the Key entity. - [EditorBrowsable(EditorBrowsableState.Never)] - public class OpenIddictEntityFrameworkScopeConfiguration : EntityTypeConfiguration - where TScope : OpenIddictEntityFrameworkScope - where TKey : notnull, IEquatable + public OpenIddictEntityFrameworkScopeConfiguration() { - public OpenIddictEntityFrameworkScopeConfiguration() - { - // Warning: optional foreign keys MUST NOT be added as CLR properties because - // Entity Framework would throw an exception due to the TKey generic parameter - // being non-nullable when using value types like short, int, long or Guid. + // Warning: optional foreign keys MUST NOT be added as CLR properties because + // Entity Framework would throw an exception due to the TKey generic parameter + // being non-nullable when using value types like short, int, long or Guid. - HasKey(scope => scope.Id); + HasKey(scope => scope.Id); - Property(scope => scope.ConcurrencyToken) - .HasMaxLength(50) - .IsConcurrencyToken(); + Property(scope => scope.ConcurrencyToken) + .HasMaxLength(50) + .IsConcurrencyToken(); - Property(scope => scope.Name) - .HasMaxLength(200) - .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute - { - IsUnique = true - })); + Property(scope => scope.Name) + .HasMaxLength(200) + .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute + { + IsUnique = true + })); - ToTable("OpenIddictScopes"); - } + ToTable("OpenIddictScopes"); } } diff --git a/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkTokenConfiguration.cs b/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkTokenConfiguration.cs index 80bf63d1..b579dd5f 100644 --- a/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkTokenConfiguration.cs +++ b/src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkTokenConfiguration.cs @@ -11,51 +11,50 @@ using System.Data.Entity.Infrastructure.Annotations; using System.Data.Entity.ModelConfiguration; using OpenIddict.EntityFramework.Models; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Defines a relational mapping for the Token entity. +/// +/// The type of the Token entity. +/// The type of the Application entity. +/// The type of the Authorization entity. +/// The type of the Key entity. +[EditorBrowsable(EditorBrowsableState.Never)] +public class OpenIddictEntityFrameworkTokenConfiguration : EntityTypeConfiguration + where TToken : OpenIddictEntityFrameworkToken + where TApplication : OpenIddictEntityFrameworkApplication + where TAuthorization : OpenIddictEntityFrameworkAuthorization + where TKey : notnull, IEquatable { - /// - /// Defines a relational mapping for the Token entity. - /// - /// The type of the Token entity. - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Key entity. - [EditorBrowsable(EditorBrowsableState.Never)] - public class OpenIddictEntityFrameworkTokenConfiguration : EntityTypeConfiguration - where TToken : OpenIddictEntityFrameworkToken - where TApplication : OpenIddictEntityFrameworkApplication - where TAuthorization : OpenIddictEntityFrameworkAuthorization - where TKey : notnull, IEquatable + public OpenIddictEntityFrameworkTokenConfiguration() { - public OpenIddictEntityFrameworkTokenConfiguration() - { - // Warning: optional foreign keys MUST NOT be added as CLR properties because - // Entity Framework would throw an exception due to the TKey generic parameter - // being non-nullable when using value types like short, int, long or Guid. + // Warning: optional foreign keys MUST NOT be added as CLR properties because + // Entity Framework would throw an exception due to the TKey generic parameter + // being non-nullable when using value types like short, int, long or Guid. - HasKey(token => token.Id); + HasKey(token => token.Id); - Property(token => token.ConcurrencyToken) - .HasMaxLength(50) - .IsConcurrencyToken(); + Property(token => token.ConcurrencyToken) + .HasMaxLength(50) + .IsConcurrencyToken(); - // Warning: the index on the ReferenceId property MUST NOT be declared as - // a unique index, as Entity Framework 6.x doesn't support creating indexes - // with null-friendly WHERE conditions, unlike Entity Framework Core 1.x/2.x. - Property(token => token.ReferenceId) - .HasMaxLength(100) - .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute())); + // Warning: the index on the ReferenceId property MUST NOT be declared as + // a unique index, as Entity Framework 6.x doesn't support creating indexes + // with null-friendly WHERE conditions, unlike Entity Framework Core 1.x/2.x. + Property(token => token.ReferenceId) + .HasMaxLength(100) + .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute())); - Property(token => token.Status) - .HasMaxLength(50); + Property(token => token.Status) + .HasMaxLength(50); - Property(token => token.Subject) - .HasMaxLength(400); + Property(token => token.Subject) + .HasMaxLength(400); - Property(token => token.Type) - .HasMaxLength(50); + Property(token => token.Type) + .HasMaxLength(50); - ToTable("OpenIddictTokens"); - } + ToTable("OpenIddictTokens"); } } diff --git a/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs b/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs index 0df3c42d..0ff17419 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs @@ -13,117 +13,116 @@ using OpenIddict.EntityFramework; using OpenIddict.EntityFramework.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the OpenIddict Entity Framework 6.x services. +/// +public class OpenIddictEntityFrameworkBuilder { /// - /// Exposes the necessary methods required to configure the OpenIddict Entity Framework 6.x services. + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictEntityFrameworkBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict Entity Framework 6.x configuration. /// - public class OpenIddictEntityFrameworkBuilder + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictEntityFrameworkBuilder Configure(Action configuration) { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictEntityFrameworkBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Amends the default OpenIddict Entity Framework 6.x configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictEntityFrameworkBuilder Configure(Action configuration) + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Configures OpenIddict to use the specified entities, derived - /// from the default OpenIddict Entity Framework 6.x entities. - /// - /// The . - public OpenIddictEntityFrameworkBuilder ReplaceDefaultEntities() - where TApplication : OpenIddictEntityFrameworkApplication - where TAuthorization : OpenIddictEntityFrameworkAuthorization - where TScope : OpenIddictEntityFrameworkScope - where TToken : OpenIddictEntityFrameworkToken - where TKey : notnull, IEquatable + /// + /// Configures OpenIddict to use the specified entities, derived + /// from the default OpenIddict Entity Framework 6.x entities. + /// + /// The . + public OpenIddictEntityFrameworkBuilder ReplaceDefaultEntities() + where TApplication : OpenIddictEntityFrameworkApplication + where TAuthorization : OpenIddictEntityFrameworkAuthorization + where TScope : OpenIddictEntityFrameworkScope + where TToken : OpenIddictEntityFrameworkToken + where TKey : notnull, IEquatable + { + // Note: unlike Entity Framework Core 1.x/2.x/3.x, Entity Framework 6.x + // always throws an exception when using generic types as entity types. + // To ensure a better exception is thrown, a manual check is made here. + if (typeof(TApplication).IsGenericType || typeof(TAuthorization).IsGenericType || + typeof(TScope).IsGenericType || typeof(TToken).IsGenericType) { - // Note: unlike Entity Framework Core 1.x/2.x/3.x, Entity Framework 6.x - // always throws an exception when using generic types as entity types. - // To ensure a better exception is thrown, a manual check is made here. - if (typeof(TApplication).IsGenericType || typeof(TAuthorization).IsGenericType || - typeof(TScope).IsGenericType || typeof(TToken).IsGenericType) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0277)); - } - - Services.Configure(options => - { - options.DefaultApplicationType = typeof(TApplication); - options.DefaultAuthorizationType = typeof(TAuthorization); - options.DefaultScopeType = typeof(TScope); - options.DefaultTokenType = typeof(TToken); - }); - - return this; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0277)); } - /// - /// Configures the OpenIddict Entity Framework 6.x stores to use the specified database context type. - /// - /// The type of the used by OpenIddict. - /// The . - public OpenIddictEntityFrameworkBuilder UseDbContext() - where TContext : DbContext - => UseDbContext(typeof(TContext)); - - /// - /// Configures the OpenIddict Entity Framework 6.x stores to use the specified database context type. - /// - /// The type of the used by OpenIddict. - /// The . - public OpenIddictEntityFrameworkBuilder UseDbContext(Type type) + Services.Configure(options => { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + options.DefaultApplicationType = typeof(TApplication); + options.DefaultAuthorizationType = typeof(TAuthorization); + options.DefaultScopeType = typeof(TScope); + options.DefaultTokenType = typeof(TToken); + }); - if (!typeof(DbContext).IsAssignableFrom(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + return this; + } - Services.TryAddScoped(type); + /// + /// Configures the OpenIddict Entity Framework 6.x stores to use the specified database context type. + /// + /// The type of the used by OpenIddict. + /// The . + public OpenIddictEntityFrameworkBuilder UseDbContext() + where TContext : DbContext + => UseDbContext(typeof(TContext)); - return Configure(options => options.DbContextType = type); + /// + /// Configures the OpenIddict Entity Framework 6.x stores to use the specified database context type. + /// + /// The type of the used by OpenIddict. + /// The . + public OpenIddictEntityFrameworkBuilder UseDbContext(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + if (!typeof(DbContext).IsAssignableFrom(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + Services.TryAddScoped(type); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); + return Configure(options => options.DbContextType = type); } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs b/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs index ec7854e0..67876622 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs @@ -9,79 +9,78 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using OpenIddict.EntityFramework; using OpenIddict.EntityFramework.Models; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict Entity Framework 6.x services. +/// +public static class OpenIddictEntityFrameworkExtensions { /// - /// Exposes extensions allowing to register the OpenIddict Entity Framework 6.x services. + /// Registers the Entity Framework 6.x stores services in the DI container and + /// configures OpenIddict to use the Entity Framework 6.x entities by default. /// - public static class OpenIddictEntityFrameworkExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictEntityFrameworkBuilder UseEntityFramework(this OpenIddictCoreBuilder builder) { - /// - /// Registers the Entity Framework 6.x stores services in the DI container and - /// configures OpenIddict to use the Entity Framework 6.x entities by default. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictEntityFrameworkBuilder UseEntityFramework(this OpenIddictCoreBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - // Since Entity Framework 6.x may be used with databases performing case-insensitive - // or culture-sensitive comparisons, ensure the additional filtering logic is enforced - // in case case-sensitive stores were registered before this extension was called. - builder.Configure(options => options.DisableAdditionalFiltering = false); + // Since Entity Framework 6.x may be used with databases performing case-insensitive + // or culture-sensitive comparisons, ensure the additional filtering logic is enforced + // in case case-sensitive stores were registered before this extension was called. + builder.Configure(options => options.DisableAdditionalFiltering = false); - builder.SetDefaultApplicationEntity() - .SetDefaultAuthorizationEntity() - .SetDefaultScopeEntity() - .SetDefaultTokenEntity(); + builder.SetDefaultApplicationEntity() + .SetDefaultAuthorizationEntity() + .SetDefaultScopeEntity() + .SetDefaultTokenEntity(); - builder.ReplaceApplicationStoreResolver() - .ReplaceAuthorizationStoreResolver() - .ReplaceScopeStoreResolver() - .ReplaceTokenStoreResolver(); + builder.ReplaceApplicationStoreResolver() + .ReplaceAuthorizationStoreResolver() + .ReplaceScopeStoreResolver() + .ReplaceTokenStoreResolver(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); - builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkApplicationStore<,,,,>)); - builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkAuthorizationStore<,,,,>)); - builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkScopeStore<,,>)); - builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkTokenStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkApplicationStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkAuthorizationStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkScopeStore<,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkTokenStore<,,,,>)); - return new OpenIddictEntityFrameworkBuilder(builder.Services); - } + return new OpenIddictEntityFrameworkBuilder(builder.Services); + } - /// - /// Registers the Entity Framework 6.x stores services in the DI container and - /// configures OpenIddict to use the Entity Framework 6.x entities by default. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the Entity Framework 6.x services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictCoreBuilder UseEntityFramework( - this OpenIddictCoreBuilder builder, Action configuration) + /// + /// Registers the Entity Framework 6.x stores services in the DI container and + /// configures OpenIddict to use the Entity Framework 6.x entities by default. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the Entity Framework 6.x services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictCoreBuilder UseEntityFramework( + this OpenIddictCoreBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseEntityFramework()); + configuration(builder.UseEntityFramework()); - return builder; - } + return builder; } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs b/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs index 127d93cf..9fc2f877 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs @@ -12,79 +12,78 @@ using Microsoft.Extensions.DependencyInjection; using OpenIddict.EntityFramework; using OpenIddict.EntityFramework.Models; -namespace System.Data.Entity +namespace System.Data.Entity; + +/// +/// Exposes extensions simplifying the integration between OpenIddict and Entity Framework 6.x. +/// +public static class OpenIddictEntityFrameworkHelpers { /// - /// Exposes extensions simplifying the integration between OpenIddict and Entity Framework 6.x. + /// Registers the OpenIddict entity sets in the Entity Framework 6.x context + /// using the default OpenIddict models and the default key type (string). /// - public static class OpenIddictEntityFrameworkHelpers - { - /// - /// Registers the OpenIddict entity sets in the Entity Framework 6.x context - /// using the default OpenIddict models and the default key type (string). - /// - /// The builder used to configure the Entity Framework context. - /// The Entity Framework context builder. - public static DbModelBuilder UseOpenIddict(this DbModelBuilder builder) - => builder.UseOpenIddict(); + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static DbModelBuilder UseOpenIddict(this DbModelBuilder builder) + => builder.UseOpenIddict(); - /// - /// Registers the OpenIddict entity sets in the Entity Framework 6.x - /// context using the specified entities and the specified key type. - /// - /// - /// Note: when using custom entities, the new entities MUST be registered by calling - /// . - /// - /// The builder used to configure the Entity Framework context. - /// The Entity Framework context builder. - public static DbModelBuilder UseOpenIddict(this DbModelBuilder builder) - where TApplication : OpenIddictEntityFrameworkApplication - where TAuthorization : OpenIddictEntityFrameworkAuthorization - where TScope : OpenIddictEntityFrameworkScope - where TToken : OpenIddictEntityFrameworkToken - where TKey : notnull, IEquatable + /// + /// Registers the OpenIddict entity sets in the Entity Framework 6.x + /// context using the specified entities and the specified key type. + /// + /// + /// Note: when using custom entities, the new entities MUST be registered by calling + /// . + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static DbModelBuilder UseOpenIddict(this DbModelBuilder builder) + where TApplication : OpenIddictEntityFrameworkApplication + where TAuthorization : OpenIddictEntityFrameworkAuthorization + where TScope : OpenIddictEntityFrameworkScope + where TToken : OpenIddictEntityFrameworkToken + where TKey : notnull, IEquatable + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Configurations - .Add(new OpenIddictEntityFrameworkApplicationConfiguration()) - .Add(new OpenIddictEntityFrameworkAuthorizationConfiguration()) - .Add(new OpenIddictEntityFrameworkScopeConfiguration()) - .Add(new OpenIddictEntityFrameworkTokenConfiguration()); + builder.Configurations + .Add(new OpenIddictEntityFrameworkApplicationConfiguration()) + .Add(new OpenIddictEntityFrameworkAuthorizationConfiguration()) + .Add(new OpenIddictEntityFrameworkScopeConfiguration()) + .Add(new OpenIddictEntityFrameworkTokenConfiguration()); - return builder; - } + return builder; + } - /// - /// Executes the query and returns the results as a non-streamed async enumeration. - /// - /// The type of the returned entities. - /// The query source. - /// The that can be used to abort the operation. - /// The non-streamed async enumeration containing the results. - internal static IAsyncEnumerable AsAsyncEnumerable(this IQueryable source, CancellationToken cancellationToken) + /// + /// Executes the query and returns the results as a non-streamed async enumeration. + /// + /// The type of the returned entities. + /// The query source. + /// The that can be used to abort the operation. + /// The non-streamed async enumeration containing the results. + internal static IAsyncEnumerable AsAsyncEnumerable(this IQueryable source, CancellationToken cancellationToken) + { + if (source is null) { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } + throw new ArgumentNullException(nameof(source)); + } - return ExecuteAsync(source, cancellationToken); + return ExecuteAsync(source, cancellationToken); - static async IAsyncEnumerable ExecuteAsync(IQueryable source, [EnumeratorCancellation] CancellationToken cancellationToken) + static async IAsyncEnumerable ExecuteAsync(IQueryable source, [EnumeratorCancellation] CancellationToken cancellationToken) + { + foreach (var element in await source.ToListAsync(cancellationToken)) { - foreach (var element in await source.ToListAsync(cancellationToken)) - { - yield return element; - } + yield return element; } } } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkOptions.cs b/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkOptions.cs index 083795c1..52c84dac 100644 --- a/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkOptions.cs +++ b/src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkOptions.cs @@ -7,19 +7,18 @@ using System; using System.Data.Entity; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Provides various settings needed to configure +/// the OpenIddict Entity Framework 6.x integration. +/// +public class OpenIddictEntityFrameworkOptions { /// - /// Provides various settings needed to configure - /// the OpenIddict Entity Framework 6.x integration. + /// Gets or sets the concrete type of the used by the + /// OpenIddict Entity Framework 6.x stores. If this property is not populated, + /// an exception is thrown at runtime when trying to use the stores. /// - public class OpenIddictEntityFrameworkOptions - { - /// - /// Gets or sets the concrete type of the used by the - /// OpenIddict Entity Framework 6.x stores. If this property is not populated, - /// an exception is thrown at runtime when trying to use the stores. - /// - public Type? DbContextType { get; set; } - } + public Type? DbContextType { get; set; } } diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolver.cs index 76409bea..89b8931e 100644 --- a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolver.cs +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolver.cs @@ -13,70 +13,69 @@ using OpenIddict.EntityFramework.Models; using OpenIddict.Extensions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Exposes a method allowing to resolve an application store. +/// +public class OpenIddictEntityFrameworkApplicationStoreResolver : IOpenIddictApplicationStoreResolver { + private readonly TypeResolutionCache _cache; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + public OpenIddictEntityFrameworkApplicationStoreResolver( + TypeResolutionCache cache, + IOptionsMonitor options, + IServiceProvider provider) + { + _cache = cache; + _options = options; + _provider = provider; + } + /// - /// Exposes a method allowing to resolve an application store. + /// Returns an application store compatible with the specified application type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictEntityFrameworkApplicationStoreResolver : IOpenIddictApplicationStoreResolver + /// The type of the Application entity. + /// An . + public IOpenIddictApplicationStore Get() where TApplication : class { - private readonly TypeResolutionCache _cache; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - public OpenIddictEntityFrameworkApplicationStoreResolver( - TypeResolutionCache cache, - IOptionsMonitor options, - IServiceProvider provider) + var store = _provider.GetService>(); + if (store is not null) { - _cache = cache; - _options = options; - _provider = provider; + return store; } - /// - /// Returns an application store compatible with the specified application type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Application entity. - /// An . - public IOpenIddictApplicationStore Get() where TApplication : class + var type = _cache.GetOrAdd(typeof(TApplication), key => { - var store = _provider.GetService>(); - if (store is not null) + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkApplication<,,>)); + if (root is null) { - return store; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0234)); } - var type = _cache.GetOrAdd(typeof(TApplication), key => + var context = _options.CurrentValue.DbContextType; + if (context is null) { - var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkApplication<,,>)); - if (root is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0234)); - } - - var context = _options.CurrentValue.DbContextType; - if (context is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0235)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0235)); + } - return typeof(OpenIddictEntityFrameworkApplicationStore<,,,,>).MakeGenericType( - /* TApplication: */ key, - /* TAuthorization: */ root.GenericTypeArguments[1], - /* TToken: */ root.GenericTypeArguments[2], - /* TContext: */ context, - /* TKey: */ root.GenericTypeArguments[0]); - }); + return typeof(OpenIddictEntityFrameworkApplicationStore<,,,,>).MakeGenericType( + /* TApplication: */ key, + /* TAuthorization: */ root.GenericTypeArguments[1], + /* TToken: */ root.GenericTypeArguments[2], + /* TContext: */ context, + /* TKey: */ root.GenericTypeArguments[0]); + }); - return (IOpenIddictApplicationStore) _provider.GetRequiredService(type); - } - - // Note: Entity Framework resolvers are registered as scoped dependencies as their inner - // service provider must be able to resolve scoped services (typically, the store they return). - // To avoid having to declare a static type resolution cache, a special cache service is used - // here and registered as a singleton dependency so that its content persists beyond the scope. - public class TypeResolutionCache : ConcurrentDictionary { } + return (IOpenIddictApplicationStore) _provider.GetRequiredService(type); } + + // Note: Entity Framework resolvers are registered as scoped dependencies as their inner + // service provider must be able to resolve scoped services (typically, the store they return). + // To avoid having to declare a static type resolution cache, a special cache service is used + // here and registered as a singleton dependency so that its content persists beyond the scope. + public class TypeResolutionCache : ConcurrentDictionary { } } diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolver.cs index c40e8128..a0fc6da3 100644 --- a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolver.cs +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolver.cs @@ -13,70 +13,69 @@ using OpenIddict.EntityFramework.Models; using OpenIddict.Extensions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Exposes a method allowing to resolve an authorization store. +/// +public class OpenIddictEntityFrameworkAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver { + private readonly TypeResolutionCache _cache; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + public OpenIddictEntityFrameworkAuthorizationStoreResolver( + TypeResolutionCache cache, + IOptionsMonitor options, + IServiceProvider provider) + { + _cache = cache; + _options = options; + _provider = provider; + } + /// - /// Exposes a method allowing to resolve an authorization store. + /// Returns an authorization store compatible with the specified authorization type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictEntityFrameworkAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver + /// The type of the Authorization entity. + /// An . + public IOpenIddictAuthorizationStore Get() where TAuthorization : class { - private readonly TypeResolutionCache _cache; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - public OpenIddictEntityFrameworkAuthorizationStoreResolver( - TypeResolutionCache cache, - IOptionsMonitor options, - IServiceProvider provider) + var store = _provider.GetService>(); + if (store is not null) { - _cache = cache; - _options = options; - _provider = provider; + return store; } - /// - /// Returns an authorization store compatible with the specified authorization type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Authorization entity. - /// An . - public IOpenIddictAuthorizationStore Get() where TAuthorization : class + var type = _cache.GetOrAdd(typeof(TAuthorization), key => { - var store = _provider.GetService>(); - if (store is not null) + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkAuthorization<,,>)); + if (root is null) { - return store; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0236)); } - var type = _cache.GetOrAdd(typeof(TAuthorization), key => + var context = _options.CurrentValue.DbContextType; + if (context is null) { - var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkAuthorization<,,>)); - if (root is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0236)); - } - - var context = _options.CurrentValue.DbContextType; - if (context is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0235)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0235)); + } - return typeof(OpenIddictEntityFrameworkAuthorizationStore<,,,,>).MakeGenericType( - /* TAuthorization: */ key, - /* TApplication: */ root.GenericTypeArguments[1], - /* TToken: */ root.GenericTypeArguments[2], - /* TContext: */ context, - /* TKey: */ root.GenericTypeArguments[0]); - }); + return typeof(OpenIddictEntityFrameworkAuthorizationStore<,,,,>).MakeGenericType( + /* TAuthorization: */ key, + /* TApplication: */ root.GenericTypeArguments[1], + /* TToken: */ root.GenericTypeArguments[2], + /* TContext: */ context, + /* TKey: */ root.GenericTypeArguments[0]); + }); - return (IOpenIddictAuthorizationStore) _provider.GetRequiredService(type); - } - - // Note: Entity Framework resolvers are registered as scoped dependencies as their inner - // service provider must be able to resolve scoped services (typically, the store they return). - // To avoid having to declare a static type resolution cache, a special cache service is used - // here and registered as a singleton dependency so that its content persists beyond the scope. - public class TypeResolutionCache : ConcurrentDictionary { } + return (IOpenIddictAuthorizationStore) _provider.GetRequiredService(type); } + + // Note: Entity Framework resolvers are registered as scoped dependencies as their inner + // service provider must be able to resolve scoped services (typically, the store they return). + // To avoid having to declare a static type resolution cache, a special cache service is used + // here and registered as a singleton dependency so that its content persists beyond the scope. + public class TypeResolutionCache : ConcurrentDictionary { } } diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkScopeStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkScopeStoreResolver.cs index 2957fcf3..f583c833 100644 --- a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkScopeStoreResolver.cs +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkScopeStoreResolver.cs @@ -13,68 +13,67 @@ using OpenIddict.EntityFramework.Models; using OpenIddict.Extensions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Exposes a method allowing to resolve a scope store. +/// +public class OpenIddictEntityFrameworkScopeStoreResolver : IOpenIddictScopeStoreResolver { + private readonly TypeResolutionCache _cache; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + public OpenIddictEntityFrameworkScopeStoreResolver( + TypeResolutionCache cache, + IOptionsMonitor options, + IServiceProvider provider) + { + _cache = cache; + _options = options; + _provider = provider; + } + /// - /// Exposes a method allowing to resolve a scope store. + /// Returns a scope store compatible with the specified scope type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictEntityFrameworkScopeStoreResolver : IOpenIddictScopeStoreResolver + /// The type of the Scope entity. + /// An . + public IOpenIddictScopeStore Get() where TScope : class { - private readonly TypeResolutionCache _cache; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - public OpenIddictEntityFrameworkScopeStoreResolver( - TypeResolutionCache cache, - IOptionsMonitor options, - IServiceProvider provider) + var store = _provider.GetService>(); + if (store is not null) { - _cache = cache; - _options = options; - _provider = provider; + return store; } - /// - /// Returns a scope store compatible with the specified scope type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Scope entity. - /// An . - public IOpenIddictScopeStore Get() where TScope : class + var type = _cache.GetOrAdd(typeof(TScope), key => { - var store = _provider.GetService>(); - if (store is not null) + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkScope<>)); + if (root is null) { - return store; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0237)); } - var type = _cache.GetOrAdd(typeof(TScope), key => + var context = _options.CurrentValue.DbContextType; + if (context is null) { - var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkScope<>)); - if (root is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0237)); - } - - var context = _options.CurrentValue.DbContextType; - if (context is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0235)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0235)); + } - return typeof(OpenIddictEntityFrameworkScopeStore<,,>).MakeGenericType( - /* TScope: */ key, - /* TContext: */ context, - /* TKey: */ root.GenericTypeArguments[0]); - }); + return typeof(OpenIddictEntityFrameworkScopeStore<,,>).MakeGenericType( + /* TScope: */ key, + /* TContext: */ context, + /* TKey: */ root.GenericTypeArguments[0]); + }); - return (IOpenIddictScopeStore) _provider.GetRequiredService(type); - } - - // Note: Entity Framework resolvers are registered as scoped dependencies as their inner - // service provider must be able to resolve scoped services (typically, the store they return). - // To avoid having to declare a static type resolution cache, a special cache service is used - // here and registered as a singleton dependency so that its content persists beyond the scope. - public class TypeResolutionCache : ConcurrentDictionary { } + return (IOpenIddictScopeStore) _provider.GetRequiredService(type); } + + // Note: Entity Framework resolvers are registered as scoped dependencies as their inner + // service provider must be able to resolve scoped services (typically, the store they return). + // To avoid having to declare a static type resolution cache, a special cache service is used + // here and registered as a singleton dependency so that its content persists beyond the scope. + public class TypeResolutionCache : ConcurrentDictionary { } } diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkTokenStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkTokenStoreResolver.cs index 0884ae90..9687f9ff 100644 --- a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkTokenStoreResolver.cs +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkTokenStoreResolver.cs @@ -13,70 +13,69 @@ using OpenIddict.EntityFramework.Models; using OpenIddict.Extensions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Exposes a method allowing to resolve a token store. +/// +public class OpenIddictEntityFrameworkTokenStoreResolver : IOpenIddictTokenStoreResolver { + private readonly TypeResolutionCache _cache; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + public OpenIddictEntityFrameworkTokenStoreResolver( + TypeResolutionCache cache, + IOptionsMonitor options, + IServiceProvider provider) + { + _cache = cache; + _options = options; + _provider = provider; + } + /// - /// Exposes a method allowing to resolve a token store. + /// Returns a token store compatible with the specified token type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictEntityFrameworkTokenStoreResolver : IOpenIddictTokenStoreResolver + /// The type of the Token entity. + /// An . + public IOpenIddictTokenStore Get() where TToken : class { - private readonly TypeResolutionCache _cache; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - public OpenIddictEntityFrameworkTokenStoreResolver( - TypeResolutionCache cache, - IOptionsMonitor options, - IServiceProvider provider) + var store = _provider.GetService>(); + if (store is not null) { - _cache = cache; - _options = options; - _provider = provider; + return store; } - /// - /// Returns a token store compatible with the specified token type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Token entity. - /// An . - public IOpenIddictTokenStore Get() where TToken : class + var type = _cache.GetOrAdd(typeof(TToken), key => { - var store = _provider.GetService>(); - if (store is not null) + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkToken<,,>)); + if (root is null) { - return store; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0238)); } - var type = _cache.GetOrAdd(typeof(TToken), key => + var context = _options.CurrentValue.DbContextType; + if (context is null) { - var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkToken<,,>)); - if (root is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0238)); - } - - var context = _options.CurrentValue.DbContextType; - if (context is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0235)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0235)); + } - return typeof(OpenIddictEntityFrameworkTokenStore<,,,,>).MakeGenericType( - /* TToken: */ key, - /* TApplication: */ root.GenericTypeArguments[1], - /* TAuthorization: */ root.GenericTypeArguments[2], - /* TContext: */ context, - /* TKey: */ root.GenericTypeArguments[0]); - }); + return typeof(OpenIddictEntityFrameworkTokenStore<,,,,>).MakeGenericType( + /* TToken: */ key, + /* TApplication: */ root.GenericTypeArguments[1], + /* TAuthorization: */ root.GenericTypeArguments[2], + /* TContext: */ context, + /* TKey: */ root.GenericTypeArguments[0]); + }); - return (IOpenIddictTokenStore) _provider.GetRequiredService(type); - } - - // Note: Entity Framework resolvers are registered as scoped dependencies as their inner - // service provider must be able to resolve scoped services (typically, the store they return). - // To avoid having to declare a static type resolution cache, a special cache service is used - // here and registered as a singleton dependency so that its content persists beyond the scope. - public class TypeResolutionCache : ConcurrentDictionary { } + return (IOpenIddictTokenStore) _provider.GetRequiredService(type); } + + // Note: Entity Framework resolvers are registered as scoped dependencies as their inner + // service provider must be able to resolve scoped services (typically, the store they return). + // To avoid having to declare a static type resolution cache, a special cache service is used + // here and registered as a singleton dependency so that its content persists beyond the scope. + public class TypeResolutionCache : ConcurrentDictionary { } } diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs index bb0c38f8..81777bb0 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs @@ -26,1007 +26,1006 @@ using OpenIddict.Abstractions; using OpenIddict.EntityFramework.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Provides methods allowing to manage the applications stored in a database. +/// +/// The type of the Entity Framework database context. +public class OpenIddictEntityFrameworkApplicationStore : + OpenIddictEntityFrameworkApplicationStore + where TContext : DbContext { + public OpenIddictEntityFrameworkApplicationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) + { + } +} + +/// +/// Provides methods allowing to manage the applications stored in a database. +/// +/// The type of the Application entity. +/// The type of the Authorization entity. +/// The type of the Token entity. +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkApplicationStore : IOpenIddictApplicationStore + where TApplication : OpenIddictEntityFrameworkApplication + where TAuthorization : OpenIddictEntityFrameworkAuthorization + where TToken : OpenIddictEntityFrameworkToken + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkApplicationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + { + Cache = cache; + Context = context; + Options = options; + } + + /// + /// Gets the memory cache associated with the current store. + /// + protected IMemoryCache Cache { get; } + + /// + /// Gets the database context associated with the current store. + /// + protected TContext Context { get; } + + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + /// - /// Provides methods allowing to manage the applications stored in a database. + /// Gets the database set corresponding to the entity. /// - /// The type of the Entity Framework database context. - public class OpenIddictEntityFrameworkApplicationStore : - OpenIddictEntityFrameworkApplicationStore - where TContext : DbContext + private DbSet Applications => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Authorizations => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Tokens => Context.Set(); + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + => await Applications.LongCountAsync(cancellationToken); + + /// + public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkApplicationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) + if (query is null) { + throw new ArgumentNullException(nameof(query)); } + + return await query(Applications).LongCountAsync(cancellationToken); } - /// - /// Provides methods allowing to manage the applications stored in a database. - /// - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Token entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkApplicationStore : IOpenIddictApplicationStore - where TApplication : OpenIddictEntityFrameworkApplication - where TAuthorization : OpenIddictEntityFrameworkAuthorization - where TToken : OpenIddictEntityFrameworkToken - where TContext : DbContext - where TKey : notnull, IEquatable + /// + public virtual async ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkApplicationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - { - Cache = cache; - Context = context; - Options = options; - } - - /// - /// Gets the memory cache associated with the current store. - /// - protected IMemoryCache Cache { get; } - - /// - /// Gets the database context associated with the current store. - /// - protected TContext Context { get; } - - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Applications => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Authorizations => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Tokens => Context.Set(); - - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) - => await Applications.LongCountAsync(cancellationToken); - - /// - public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return await query(Applications).LongCountAsync(cancellationToken); + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual async ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Applications.Add(application); - Applications.Add(application); + await Context.SaveChangesAsync(cancellationToken); + } - await Context.SaveChangesAsync(cancellationToken); + /// + public virtual async ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual async ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken) + DbContextTransaction? CreateTransaction() { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - DbContextTransaction? CreateTransaction() + try { - try - { - return Context.Database.BeginTransaction(IsolationLevel.Serializable); - } - - catch - { - return null; - } + return Context.Database.BeginTransaction(IsolationLevel.Serializable); } - Task> ListAuthorizationsAsync() - => (from authorization in Authorizations.Include(authorization => authorization.Tokens) - where authorization.Application!.Id!.Equals(application.Id) - select authorization).ToListAsync(cancellationToken); - - Task> ListTokensAsync() - => (from token in Tokens - where token.Authorization == null - where token.Application!.Id!.Equals(application.Id) - select token).ToListAsync(cancellationToken); - - // To prevent an SQL exception from being thrown if a new associated entity is - // created after the existing entries have been listed, the following logic is - // executed in a serializable transaction, that will lock the affected tables. - using var transaction = CreateTransaction(); - - // Remove all the authorizations associated with the application and - // the tokens attached to these implicit or explicit authorizations. - var authorizations = await ListAuthorizationsAsync(); - foreach (var authorization in authorizations) + catch { - foreach (var token in authorization.Tokens) - { - Tokens.Remove(token); - } - - Authorizations.Remove(authorization); + return null; } + } - // Remove all the tokens associated with the application. - var tokens = await ListTokensAsync(); - foreach (var token in tokens) + Task> ListAuthorizationsAsync() + => (from authorization in Authorizations.Include(authorization => authorization.Tokens) + where authorization.Application!.Id!.Equals(application.Id) + select authorization).ToListAsync(cancellationToken); + + Task> ListTokensAsync() + => (from token in Tokens + where token.Authorization == null + where token.Application!.Id!.Equals(application.Id) + select token).ToListAsync(cancellationToken); + + // To prevent an SQL exception from being thrown if a new associated entity is + // created after the existing entries have been listed, the following logic is + // executed in a serializable transaction, that will lock the affected tables. + using var transaction = CreateTransaction(); + + // Remove all the authorizations associated with the application and + // the tokens attached to these implicit or explicit authorizations. + var authorizations = await ListAuthorizationsAsync(); + foreach (var authorization in authorizations) + { + foreach (var token in authorization.Tokens) { Tokens.Remove(token); } - Applications.Remove(application); - - try - { - await Context.SaveChangesAsync(cancellationToken); - transaction?.Commit(); - } - - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(application).State = EntityState.Unchanged; + Authorizations.Remove(authorization); + } - foreach (var authorization in authorizations) - { - Context.Entry(authorization).State = EntityState.Unchanged; - } + // Remove all the tokens associated with the application. + var tokens = await ListTokensAsync(); + foreach (var token in tokens) + { + Tokens.Remove(token); + } - foreach (var token in tokens) - { - Context.Entry(token).State = EntityState.Unchanged; - } + Applications.Remove(application); - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239), exception); - } + try + { + await Context.SaveChangesAsync(cancellationToken); + transaction?.Commit(); } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + catch (DbUpdateConcurrencyException exception) { - if (string.IsNullOrEmpty(identifier)) + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(application).State = EntityState.Unchanged; + + foreach (var authorization in authorizations) { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + Context.Entry(authorization).State = EntityState.Unchanged; } - var key = ConvertIdentifierFromString(identifier); + foreach (var token in tokens) + { + Context.Entry(token).State = EntityState.Unchanged; + } - return await (from application in Applications - where application.Id!.Equals(key) - select application).FirstOrDefaultAsync(cancellationToken); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239), exception); } + } - /// - public virtual async ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken) + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } + + var key = ConvertIdentifierFromString(identifier); - return await (from application in Applications - where application.ClientId == identifier - select application).FirstOrDefaultAsync(cancellationToken); + return await (from application in Applications + where application.Id!.Equals(key) + select application).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual async ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual IAsyncEnumerable FindByPostLogoutRedirectUriAsync( - string address, CancellationToken cancellationToken) + return await (from application in Applications + where application.ClientId == identifier + select application).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindByPostLogoutRedirectUriAsync( + string address, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(address)) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } - // To optimize the efficiency of the query a bit, only applications whose stringified - // PostLogoutRedirectUris contains the specified URL are returned. Once the applications - // are retrieved, a second pass is made to ensure only valid elements are returned. - // Implementers that use this method in a hot path may want to override this method - // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. + // To optimize the efficiency of the query a bit, only applications whose stringified + // PostLogoutRedirectUris contains the specified URL are returned. Once the applications + // are retrieved, a second pass is made to ensure only valid elements are returned. + // Implementers that use this method in a hot path may want to override this method + // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var applications = (from application in Applications - where application.PostLogoutRedirectUris!.Contains(address) - select application).AsAsyncEnumerable(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var applications = (from application in Applications + where application.PostLogoutRedirectUris!.Contains(address) + select application).AsAsyncEnumerable(cancellationToken); - await foreach (var application in applications) + await foreach (var application in applications) + { + var addresses = await GetPostLogoutRedirectUrisAsync(application, cancellationToken); + if (addresses.Contains(address, StringComparer.Ordinal)) { - var addresses = await GetPostLogoutRedirectUrisAsync(application, cancellationToken); - if (addresses.Contains(address, StringComparer.Ordinal)) - { - yield return application; - } + yield return application; } } } + } - /// - public virtual IAsyncEnumerable FindByRedirectUriAsync( - string address, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindByRedirectUriAsync( + string address, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(address)) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } - // To optimize the efficiency of the query a bit, only applications whose stringified - // RedirectUris property contains the specified URL are returned. Once the applications - // are retrieved, a second pass is made to ensure only valid elements are returned. - // Implementers that use this method in a hot path may want to override this method - // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. + // To optimize the efficiency of the query a bit, only applications whose stringified + // RedirectUris property contains the specified URL are returned. Once the applications + // are retrieved, a second pass is made to ensure only valid elements are returned. + // Implementers that use this method in a hot path may want to override this method + // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var applications = (from application in Applications - where application.RedirectUris!.Contains(address) - select application).AsAsyncEnumerable(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var applications = (from application in Applications + where application.RedirectUris!.Contains(address) + select application).AsAsyncEnumerable(cancellationToken); - await foreach (var application in applications) + await foreach (var application in applications) + { + var addresses = await GetRedirectUrisAsync(application, cancellationToken); + if (addresses.Contains(address, StringComparer.Ordinal)) { - var addresses = await GetRedirectUrisAsync(application, cancellationToken); - if (addresses.Contains(address, StringComparer.Ordinal)) - { - yield return application; - } + yield return application; } } } + } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return await query(Applications, state).FirstOrDefaultAsync(cancellationToken); + throw new ArgumentNullException(nameof(query)); } - /// - public virtual ValueTask GetClientIdAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return await query(Applications, state).FirstOrDefaultAsync(cancellationToken); + } - return new ValueTask(application.ClientId); + /// + public virtual ValueTask GetClientIdAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetClientSecretAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask(application.ClientId); + } - return new ValueTask(application.ClientSecret); + /// + public virtual ValueTask GetClientSecretAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask(application.ClientSecret); + } - return new ValueTask(application.Type); + /// + public virtual ValueTask GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetConsentTypeAsync(TApplication application, CancellationToken cancellationToken) + return new ValueTask(application.Type); + } + + /// + public virtual ValueTask GetConsentTypeAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask(application.ConsentType); + return new ValueTask(application.ConsentType); + } + + /// + public virtual ValueTask GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) + return new ValueTask(application.DisplayName); + } + + /// + public virtual ValueTask> GetDisplayNamesAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask(application.DisplayName); + if (string.IsNullOrEmpty(application.DisplayNames)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetDisplayNamesAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified display names is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("7762c378-c113-4564-b14b-1402b3949aaa", "\x1e", application.DisplayNames); + var names = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.DisplayNames)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(application.DisplayNames); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified display names is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("7762c378-c113-4564-b14b-1402b3949aaa", "\x1e", application.DisplayNames); - var names = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.DisplayNames); - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var property in document.RootElement.EnumerateObject()) + var value = property.Value.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = property.Value.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder[CultureInfo.GetCultureInfo(property.Name)] = value; + continue; } - return builder.ToImmutable(); - }); + builder[CultureInfo.GetCultureInfo(property.Name)] = value; + } + + return builder.ToImmutable(); + }); + + return new ValueTask>(names); + } - return new ValueTask>(names); + /// + public virtual ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken) + return new ValueTask(ConvertIdentifierToString(application.Id)); + } + + /// + public virtual ValueTask> GetPermissionsAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask(ConvertIdentifierToString(application.Id)); + if (string.IsNullOrEmpty(application.Permissions)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetPermissionsAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified permissions is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions); + var permissions = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.Permissions)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(application.Permissions); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified permissions is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions); - var permissions = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) + { + continue; + } - using var document = JsonDocument.Parse(application.Permissions); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); + builder.Add(value); + } - foreach (var element in document.RootElement.EnumerateArray()) - { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } + return builder.ToImmutable(); + }); - builder.Add(value); - } + return new ValueTask>(permissions); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetPostLogoutRedirectUrisAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask>(permissions); + if (string.IsNullOrEmpty(application.PostLogoutRedirectUris)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetPostLogoutRedirectUrisAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified addresses is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris); + var addresses = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.PostLogoutRedirectUris)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(application.PostLogoutRedirectUris); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified addresses is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris); - var addresses = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.PostLogoutRedirectUris); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder.Add(value); + continue; } - return builder.ToImmutable(); - }); - - return new ValueTask>(addresses); - } - - /// - public virtual ValueTask> GetPropertiesAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); + builder.Add(value); } - if (string.IsNullOrEmpty(application.Properties)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + return builder.ToImmutable(); + }); - // Note: parsing the stringified properties is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("2e3e9680-5654-48d8-a27d-b8bb4f0f1d50", "\x1e", application.Properties); - var properties = Cache.GetOrCreate(key, entry => - { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.Properties); - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return new ValueTask>(addresses); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetPropertiesAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask>(properties); + if (string.IsNullOrEmpty(application.Properties)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetRedirectUrisAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified properties is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("2e3e9680-5654-48d8-a27d-b8bb4f0f1d50", "\x1e", application.Properties); + var properties = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.RedirectUris)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(application.Properties); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified addresses is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris); - var addresses = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.RedirectUris); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); + builder[property.Name] = property.Value.Clone(); + } - foreach (var element in document.RootElement.EnumerateArray()) - { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } + return builder.ToImmutable(); + }); - builder.Add(value); - } + return new ValueTask>(properties); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetRedirectUrisAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask>(addresses); + if (string.IsNullOrEmpty(application.RedirectUris)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetRequirementsAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified addresses is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris); + var addresses = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.Requirements)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(application.RedirectUris); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified requirements is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("b4808a89-8969-4512-895f-a909c62a8995", "\x1e", application.Requirements); - var requirements = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.Requirements); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder.Add(value); + continue; } - return builder.ToImmutable(); - }); + builder.Add(value); + } - return new ValueTask>(requirements); - } + return builder.ToImmutable(); + }); - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + return new ValueTask>(addresses); + } + + /// + public virtual ValueTask> GetRequirementsAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - try - { - return new ValueTask(Activator.CreateInstance()); - } + throw new ArgumentNullException(nameof(application)); + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0240), exception))); - } + if (string.IsNullOrEmpty(application.Requirements)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual IAsyncEnumerable ListAsync( - int? count, int? offset, CancellationToken cancellationToken) + // Note: parsing the stringified requirements is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("b4808a89-8969-4512-895f-a909c62a8995", "\x1e", application.Requirements); + var requirements = Cache.GetOrCreate(key, entry => { - var query = Applications.OrderBy(application => application.Id!).AsQueryable(); + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } + using var document = JsonDocument.Parse(application.Requirements); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - if (count.HasValue) + foreach (var element in document.RootElement.EnumerateArray()) { - query = query.Take(count.Value); + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) + { + continue; + } + + builder.Add(value); } - return query.AsAsyncEnumerable(cancellationToken); - } + return builder.ToImmutable(); + }); - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + return new ValueTask>(requirements); + } - return query(Applications, state).AsAsyncEnumerable(cancellationToken); + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try + { + return new ValueTask(Activator.CreateInstance()); } - /// - public virtual ValueTask SetClientIdAsync(TApplication application, string? identifier, CancellationToken cancellationToken) + catch (MemberAccessException exception) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0240), exception))); + } + } - application.ClientId = identifier; + /// + public virtual IAsyncEnumerable ListAsync( + int? count, int? offset, CancellationToken cancellationToken) + { + var query = Applications.OrderBy(application => application.Id!).AsQueryable(); - return default; + if (offset.HasValue) + { + query = query.Skip(offset.Value); } - /// - public virtual ValueTask SetClientSecretAsync(TApplication application, string? secret, CancellationToken cancellationToken) + if (count.HasValue) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + query = query.Take(count.Value); + } - application.ClientSecret = secret; + return query.AsAsyncEnumerable(cancellationToken); + } - return default; + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual ValueTask SetClientTypeAsync(TApplication application, string? type, CancellationToken cancellationToken) + return query(Applications, state).AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual ValueTask SetClientIdAsync(TApplication application, string? identifier, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - application.Type = type; + application.ClientId = identifier; - return default; - } + return default; + } - /// - public virtual ValueTask SetConsentTypeAsync(TApplication application, string? type, CancellationToken cancellationToken) + /// + public virtual ValueTask SetClientSecretAsync(TApplication application, string? secret, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - application.ConsentType = type; + application.ClientSecret = secret; - return default; - } + return default; + } - /// - public virtual ValueTask SetDisplayNameAsync(TApplication application, string? name, CancellationToken cancellationToken) + /// + public virtual ValueTask SetClientTypeAsync(TApplication application, string? type, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - application.DisplayName = name; + application.Type = type; - return default; - } + return default; + } - /// - public virtual ValueTask SetDisplayNamesAsync(TApplication application, - ImmutableDictionary names, CancellationToken cancellationToken) + /// + public virtual ValueTask SetConsentTypeAsync(TApplication application, string? type, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - if (names is null || names.IsEmpty) - { - application.DisplayNames = null; + application.ConsentType = type; - return default; - } + return default; + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + /// + public virtual ValueTask SetDisplayNameAsync(TApplication application, string? name, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - writer.WriteStartObject(); + application.DisplayName = name; - foreach (var name in names) - { - writer.WritePropertyName(name.Key.Name); - writer.WriteStringValue(name.Value); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetDisplayNamesAsync(TApplication application, + ImmutableDictionary names, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.DisplayNames = Encoding.UTF8.GetString(stream.ToArray()); + if (names is null || names.IsEmpty) + { + application.DisplayNames = null; return default; } - /// - public virtual ValueTask SetPermissionsAsync(TApplication application, ImmutableArray permissions, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (permissions.IsDefaultOrEmpty) - { - application.Permissions = null; + writer.WriteStartObject(); - return default; - } + foreach (var name in names) + { + writer.WritePropertyName(name.Key.Name); + writer.WriteStringValue(name.Value); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartArray(); + application.DisplayNames = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var permission in permissions) - { - writer.WriteStringValue(permission); - } + return default; + } - writer.WriteEndArray(); - writer.Flush(); + /// + public virtual ValueTask SetPermissionsAsync(TApplication application, ImmutableArray permissions, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.Permissions = Encoding.UTF8.GetString(stream.ToArray()); + if (permissions.IsDefaultOrEmpty) + { + application.Permissions = null; return default; } - /// - public virtual ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, - ImmutableArray addresses, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (addresses.IsDefaultOrEmpty) - { - application.PostLogoutRedirectUris = null; + writer.WriteStartArray(); - return default; - } + foreach (var permission in permissions) + { + writer.WriteStringValue(permission); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndArray(); + writer.Flush(); - writer.WriteStartArray(); + application.Permissions = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var address in addresses) - { - writer.WriteStringValue(address); - } + return default; + } - writer.WriteEndArray(); - writer.Flush(); + /// + public virtual ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, + ImmutableArray addresses, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.PostLogoutRedirectUris = Encoding.UTF8.GetString(stream.ToArray()); + if (addresses.IsDefaultOrEmpty) + { + application.PostLogoutRedirectUris = null; return default; } - /// - public virtual ValueTask SetPropertiesAsync(TApplication application, - ImmutableDictionary properties, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (properties is null || properties.IsEmpty) - { - application.Properties = null; + writer.WriteStartArray(); - return default; - } + foreach (var address in addresses) + { + writer.WriteStringValue(address); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndArray(); + writer.Flush(); - writer.WriteStartObject(); + application.PostLogoutRedirectUris = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetPropertiesAsync(TApplication application, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.Properties = Encoding.UTF8.GetString(stream.ToArray()); + if (properties is null || properties.IsEmpty) + { + application.Properties = null; return default; } - /// - public virtual ValueTask SetRedirectUrisAsync(TApplication application, - ImmutableArray addresses, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (addresses.IsDefaultOrEmpty) - { - application.RedirectUris = null; + writer.WriteStartObject(); - return default; - } + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartArray(); + application.Properties = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var address in addresses) - { - writer.WriteStringValue(address); - } + return default; + } - writer.WriteEndArray(); - writer.Flush(); + /// + public virtual ValueTask SetRedirectUrisAsync(TApplication application, + ImmutableArray addresses, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.RedirectUris = Encoding.UTF8.GetString(stream.ToArray()); + if (addresses.IsDefaultOrEmpty) + { + application.RedirectUris = null; return default; } - /// - public virtual ValueTask SetRequirementsAsync(TApplication application, ImmutableArray requirements, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (requirements.IsDefaultOrEmpty) - { - application.Requirements = null; + writer.WriteStartArray(); - return default; - } + foreach (var address in addresses) + { + writer.WriteStringValue(address); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndArray(); + writer.Flush(); - writer.WriteStartArray(); + application.RedirectUris = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var requirement in requirements) - { - writer.WriteStringValue(requirement); - } + return default; + } - writer.WriteEndArray(); - writer.Flush(); + /// + public virtual ValueTask SetRequirementsAsync(TApplication application, ImmutableArray requirements, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.Requirements = Encoding.UTF8.GetString(stream.ToArray()); + if (requirements.IsDefaultOrEmpty) + { + application.Requirements = null; return default; } - /// - public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - Applications.Attach(application); + writer.WriteStartArray(); - // Generate a new concurrency token and attach it - // to the application before persisting the changes. - application.ConcurrencyToken = Guid.NewGuid().ToString(); + foreach (var requirement in requirements) + { + writer.WriteStringValue(requirement); + } - Context.Entry(application).State = EntityState.Modified; + writer.WriteEndArray(); + writer.Flush(); - try - { - await Context.SaveChangesAsync(cancellationToken); - } + application.Requirements = Encoding.UTF8.GetString(stream.ToArray()); - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(application).State = EntityState.Unchanged; + return default; + } - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239), exception); - } + /// + public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Converts the provided identifier to a strongly typed key object. - /// - /// The identifier to convert. - /// An instance of representing the provided identifier. - public virtual TKey? ConvertIdentifierFromString(string? identifier) + Applications.Attach(application); + + // Generate a new concurrency token and attach it + // to the application before persisting the changes. + application.ConcurrencyToken = Guid.NewGuid().ToString(); + + Context.Entry(application).State = EntityState.Modified; + + try { - if (string.IsNullOrEmpty(identifier)) - { - return default; - } + await Context.SaveChangesAsync(cancellationToken); + } - return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + catch (DbUpdateConcurrencyException exception) + { + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(application).State = EntityState.Unchanged; + + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239), exception); } + } - /// - /// Converts the provided identifier to its string representation. - /// - /// The identifier to convert. - /// A representation of the provided identifier. - public virtual string? ConvertIdentifierToString(TKey? identifier) + /// + /// Converts the provided identifier to a strongly typed key object. + /// + /// The identifier to convert. + /// An instance of representing the provided identifier. + public virtual TKey? ConvertIdentifierFromString(string? identifier) + { + if (string.IsNullOrEmpty(identifier)) { - if (Equals(identifier, default(TKey))) - { - return null; - } + return default; + } + + return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + } - return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); + /// + /// Converts the provided identifier to its string representation. + /// + /// The identifier to convert. + /// A representation of the provided identifier. + public virtual string? ConvertIdentifierToString(TKey? identifier) + { + if (Equals(identifier, default(TKey))) + { + return null; } + + return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs index 62dce217..27d78099 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs @@ -26,883 +26,882 @@ using OpenIddict.EntityFramework.Models; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Provides methods allowing to manage the authorizations stored in a database. +/// +/// The type of the Entity Framework database context. +public class OpenIddictEntityFrameworkAuthorizationStore : + OpenIddictEntityFrameworkAuthorizationStore + where TContext : DbContext { - /// - /// Provides methods allowing to manage the authorizations stored in a database. - /// - /// The type of the Entity Framework database context. - public class OpenIddictEntityFrameworkAuthorizationStore : - OpenIddictEntityFrameworkAuthorizationStore - where TContext : DbContext + public OpenIddictEntityFrameworkAuthorizationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) { - public OpenIddictEntityFrameworkAuthorizationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) - { - } + } +} + +/// +/// Provides methods allowing to manage the authorizations stored in a database. +/// +/// The type of the Authorization entity. +/// The type of the Application entity. +/// The type of the Token entity. +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkAuthorizationStore : IOpenIddictAuthorizationStore + where TAuthorization : OpenIddictEntityFrameworkAuthorization + where TApplication : OpenIddictEntityFrameworkApplication + where TToken : OpenIddictEntityFrameworkToken + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkAuthorizationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + { + Cache = cache; + Context = context; + Options = options; } /// - /// Provides methods allowing to manage the authorizations stored in a database. + /// Gets the memory cache associated with the current store. /// - /// The type of the Authorization entity. - /// The type of the Application entity. - /// The type of the Token entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkAuthorizationStore : IOpenIddictAuthorizationStore - where TAuthorization : OpenIddictEntityFrameworkAuthorization - where TApplication : OpenIddictEntityFrameworkApplication - where TToken : OpenIddictEntityFrameworkToken - where TContext : DbContext - where TKey : notnull, IEquatable - { - public OpenIddictEntityFrameworkAuthorizationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - { - Cache = cache; - Context = context; - Options = options; - } - - /// - /// Gets the memory cache associated with the current store. - /// - protected IMemoryCache Cache { get; } - - /// - /// Gets the database context associated with the current store. - /// - protected TContext Context { get; } - - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Applications => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Authorizations => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Tokens => Context.Set(); - - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) - => await Authorizations.LongCountAsync(cancellationToken); - - /// - public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + protected IMemoryCache Cache { get; } + + /// + /// Gets the database context associated with the current store. + /// + protected TContext Context { get; } + + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Applications => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Authorizations => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Tokens => Context.Set(); + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + => await Authorizations.LongCountAsync(cancellationToken); - return await query(Authorizations).LongCountAsync(cancellationToken); + /// + public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual async ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken) + return await query(Authorizations).LongCountAsync(cancellationToken); + } + + /// + public virtual async ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - Authorizations.Add(authorization); + Authorizations.Add(authorization); - await Context.SaveChangesAsync(cancellationToken); + await Context.SaveChangesAsync(cancellationToken); + } + + /// + public virtual async ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual async ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken) + DbContextTransaction? CreateTransaction() { - if (authorization is null) + try { - throw new ArgumentNullException(nameof(authorization)); + return Context.Database.BeginTransaction(IsolationLevel.Serializable); } - DbContextTransaction? CreateTransaction() + catch { - try - { - return Context.Database.BeginTransaction(IsolationLevel.Serializable); - } - - catch - { - return null; - } + return null; } + } - Task> ListTokensAsync() - => (from token in Tokens - where token.Authorization!.Id!.Equals(authorization.Id) - select token).ToListAsync(cancellationToken); + Task> ListTokensAsync() + => (from token in Tokens + where token.Authorization!.Id!.Equals(authorization.Id) + select token).ToListAsync(cancellationToken); - // To prevent an SQL exception from being thrown if a new associated entity is - // created after the existing entries have been listed, the following logic is - // executed in a serializable transaction, that will lock the affected tables. - using var transaction = CreateTransaction(); + // To prevent an SQL exception from being thrown if a new associated entity is + // created after the existing entries have been listed, the following logic is + // executed in a serializable transaction, that will lock the affected tables. + using var transaction = CreateTransaction(); - // Remove all the tokens associated with the authorization. - var tokens = await ListTokensAsync(); - foreach (var token in tokens) - { - Tokens.Remove(token); - } + // Remove all the tokens associated with the authorization. + var tokens = await ListTokensAsync(); + foreach (var token in tokens) + { + Tokens.Remove(token); + } - Authorizations.Remove(authorization); + Authorizations.Remove(authorization); - try - { - await Context.SaveChangesAsync(cancellationToken); - transaction?.Commit(); - } + try + { + await Context.SaveChangesAsync(cancellationToken); + transaction?.Commit(); + } + + catch (DbUpdateConcurrencyException exception) + { + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(authorization).State = EntityState.Unchanged; - catch (DbUpdateConcurrencyException exception) + foreach (var token in tokens) { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(authorization).State = EntityState.Unchanged; + Context.Entry(token).State = EntityState.Unchanged; + } - foreach (var token in tokens) - { - Context.Entry(token).State = EntityState.Unchanged; - } + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241), exception); + } + } - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241), exception); - } + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from authorization in Authorizations.Include(authorization => authorization.Application) + where authorization.Application!.Id!.Equals(key) && + authorization.Subject == subject + select authorization).AsAsyncEnumerable(cancellationToken); + } - return (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject - select authorization).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from authorization in Authorizations.Include(authorization => authorization.Application) + where authorization.Application!.Id!.Equals(key) && + authorization.Subject == subject && + authorization.Status == status + select authorization).AsAsyncEnumerable(cancellationToken); + } - return (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject && - authorization.Status == status - select authorization).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from authorization in Authorizations.Include(authorization => authorization.Application) + where authorization.Application!.Id!.Equals(key) && + authorization.Subject == subject && + authorization.Status == status && + authorization.Type == type + select authorization).AsAsyncEnumerable(cancellationToken); + } - return (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - select authorization).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, + ImmutableArray scopes, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var key = ConvertIdentifierFromString(client); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var key = ConvertIdentifierFromString(client); - var authorizations = (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Application!.Id!.Equals(key) && - authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - select authorization).AsAsyncEnumerable(cancellationToken); + var authorizations = (from authorization in Authorizations.Include(authorization => authorization.Application) + where authorization.Application!.Id!.Equals(key) && + authorization.Subject == subject && + authorization.Status == status && + authorization.Type == type + select authorization).AsAsyncEnumerable(cancellationToken); - await foreach (var authorization in authorizations) + await foreach (var authorization in authorizations) + { + if (new HashSet(await GetScopesAsync(authorization, cancellationToken), StringComparer.Ordinal).IsSupersetOf(scopes)) { - if (new HashSet(await GetScopesAsync(authorization, cancellationToken), StringComparer.Ordinal).IsSupersetOf(scopes)) - { - yield return authorization; - } + yield return authorization; } } } + } - /// - public virtual IAsyncEnumerable FindByApplicationIdAsync( - string identifier, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindByApplicationIdAsync( + string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } + + var key = ConvertIdentifierFromString(identifier); - var key = ConvertIdentifierFromString(identifier); + return (from authorization in Authorizations.Include(authorization => authorization.Application) + where authorization.Application!.Id!.Equals(key) + select authorization).AsAsyncEnumerable(cancellationToken); + } - return (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Application!.Id!.Equals(key) - select authorization).AsAsyncEnumerable(cancellationToken); + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + var key = ConvertIdentifierFromString(identifier); + + return await (from authorization in Authorizations.Include(authorization => authorization.Application) + where authorization.Id!.Equals(key) + select authorization).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindBySubjectAsync( + string subject, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - var key = ConvertIdentifierFromString(identifier); + return (from authorization in Authorizations.Include(authorization => authorization.Application) + where authorization.Subject == subject + select authorization).AsAsyncEnumerable(cancellationToken); + } - return await (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Id!.Equals(key) - select authorization).FirstOrDefaultAsync(cancellationToken); + /// + public virtual async ValueTask GetApplicationIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual IAsyncEnumerable FindBySubjectAsync( - string subject, CancellationToken cancellationToken) + // If the application is not attached to the authorization, try to load it manually. + if (authorization.Application is null) { - if (string.IsNullOrEmpty(subject)) + var reference = Context.Entry(authorization).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + return null; } - return (from authorization in Authorizations.Include(authorization => authorization.Application) - where authorization.Subject == subject - select authorization).AsAsyncEnumerable(cancellationToken); + await reference.LoadAsync(cancellationToken); } - /// - public virtual async ValueTask GetApplicationIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (authorization.Application is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return null; + } - // If the application is not attached to the authorization, try to load it manually. - if (authorization.Application is null) - { - var reference = Context.Entry(authorization).Reference(entry => entry.Application); - if (reference.EntityEntry.State == EntityState.Detached) - { - return null; - } + return ConvertIdentifierToString(authorization.Application.Id); + } - await reference.LoadAsync(cancellationToken); - } + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } - if (authorization.Application is null) - { - return null; - } + return await query( + Authorizations.Include(authorization => authorization.Application), state).FirstOrDefaultAsync(cancellationToken); + } - return ConvertIdentifierToString(authorization.Application.Id); + /// + public virtual ValueTask GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + if (authorization.CreationDate is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return await query( - Authorizations.Include(authorization => authorization.Application), state).FirstOrDefaultAsync(cancellationToken); + return new ValueTask(result: null); } - /// - public virtual ValueTask GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken) + return new ValueTask(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc)); + } + + /// + public virtual ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - if (authorization.CreationDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(ConvertIdentifierToString(authorization.Id)); + } - return new ValueTask(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask> GetPropertiesAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(authorization.Properties)) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - return new ValueTask(ConvertIdentifierToString(authorization.Id)); + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetPropertiesAsync(TAuthorization authorization, CancellationToken cancellationToken) + // Note: parsing the stringified properties is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("68056e1a-dbcf-412b-9a6a-d791c7dbe726", "\x1e", authorization.Properties); + var properties = Cache.GetOrCreate(key, entry => { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(authorization.Properties)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(authorization.Properties); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified properties is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("68056e1a-dbcf-412b-9a6a-d791c7dbe726", "\x1e", authorization.Properties); - var properties = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); + builder[property.Name] = property.Value.Clone(); + } - using var document = JsonDocument.Parse(authorization.Properties); - var builder = ImmutableDictionary.CreateBuilder(); + return builder.ToImmutable(); + }); - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return new ValueTask>(properties); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetScopesAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - return new ValueTask>(properties); + if (string.IsNullOrEmpty(authorization.Scopes)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetScopesAsync(TAuthorization authorization, CancellationToken cancellationToken) + // Note: parsing the stringified scopes is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("2ba4ab0f-e2ec-4d48-b3bd-28e2bb660c75", "\x1e", authorization.Scopes); + var scopes = Cache.GetOrCreate(key, entry => { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(authorization.Scopes)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(authorization.Scopes); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified scopes is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("2ba4ab0f-e2ec-4d48-b3bd-28e2bb660c75", "\x1e", authorization.Scopes); - var scopes = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) + { + continue; + } - using var document = JsonDocument.Parse(authorization.Scopes); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); + builder.Add(value); + } - foreach (var element in document.RootElement.EnumerateArray()) - { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } + return builder.ToImmutable(); + }); - builder.Add(value); - } + return new ValueTask>(scopes); + } + + /// + public virtual ValueTask GetStatusAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - return builder.ToImmutable(); - }); + return new ValueTask(authorization.Status); + } - return new ValueTask>(scopes); + /// + public virtual ValueTask GetSubjectAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask GetStatusAsync(TAuthorization authorization, CancellationToken cancellationToken) + return new ValueTask(authorization.Subject); + } + + /// + public virtual ValueTask GetTypeAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - return new ValueTask(authorization.Status); + return new ValueTask(authorization.Type); + } + + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try + { + return new ValueTask(Activator.CreateInstance()); } - /// - public virtual ValueTask GetSubjectAsync(TAuthorization authorization, CancellationToken cancellationToken) + catch (MemberAccessException exception) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0242), exception))); + } + } + + /// + public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + { + var query = Authorizations.Include(authorization => authorization.Application) + .OrderBy(authorization => authorization.Id!) + .AsQueryable(); - return new ValueTask(authorization.Subject); + if (offset.HasValue) + { + query = query.Skip(offset.Value); } - /// - public virtual ValueTask GetTypeAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (count.HasValue) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + query = query.Take(count.Value); + } + + return query.AsAsyncEnumerable(cancellationToken); + } - return new ValueTask(authorization.Type); + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + return query(Authorizations.Include(authorization => authorization.Application), state).AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + { + // Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing + // entities in a single command without having to retrieve and materialize them first. + // To work around this limitation, entities are manually listed and deleted using a batch logic. + + List? exceptions = null; + + DbContextTransaction? CreateTransaction() { + // Note: relational providers like Sqlite are known to lack proper support + // for repeatable read transactions. To ensure this method can be safely used + // with such providers, the database transaction is created in a try/catch block. try { - return new ValueTask(Activator.CreateInstance()); + return Context.Database.BeginTransaction(IsolationLevel.RepeatableRead); } - catch (MemberAccessException exception) + catch { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0242), exception))); + return null; } } - /// - public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + // Note: to avoid sending too many queries, the maximum number of elements + // that can be removed by a single call to PruneAsync() is deliberately limited. + for (var index = 0; index < 1_000; index++) { - var query = Authorizations.Include(authorization => authorization.Application) - .OrderBy(authorization => authorization.Id!) - .AsQueryable(); + cancellationToken.ThrowIfCancellationRequested(); - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } + // To prevent concurrency exceptions from being thrown if an entry is modified + // after it was retrieved from the database, the following logic is executed in + // a repeatable read transaction, that will put a lock on the retrieved entries + // and thus prevent them from being concurrently modified outside this block. + using var transaction = CreateTransaction(); - if (count.HasValue) + // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable + // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. + // To work around this limitation, the threshold represented as a DateTimeOffset + // instance is manually converted to a UTC DateTime instance outside the query. + var date = threshold.UtcDateTime; + + var authorizations = + await (from authorization in Authorizations.Include(authorization => authorization.Tokens) + where authorization.CreationDate < date + where authorization.Status != Statuses.Valid || + (authorization.Type == AuthorizationTypes.AdHoc && !authorization.Tokens.Any()) + orderby authorization.Id + select authorization).Take(1_000).ToListAsync(cancellationToken); + + if (authorizations.Count == 0) { - query = query.Take(count.Value); + break; } - return query.AsAsyncEnumerable(cancellationToken); - } + // Note: new tokens may be attached after the authorizations were retrieved + // from the database since the transaction level is deliberately limited to + // repeatable read instead of serializable for performance reasons). In this + // case, the operation will fail, which is considered an acceptable risk. + Authorizations.RemoveRange(authorizations); - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) - { - if (query is null) + try { - throw new ArgumentNullException(nameof(query)); + await Context.SaveChangesAsync(cancellationToken); + transaction?.Commit(); } - return query(Authorizations.Include(authorization => authorization.Application), state).AsAsyncEnumerable(cancellationToken); + catch (Exception exception) + { + exceptions ??= new List(capacity: 1); + exceptions.Add(exception); + } } - /// - public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + if (exceptions is not null) { - // Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing - // entities in a single command without having to retrieve and materialize them first. - // To work around this limitation, entities are manually listed and deleted using a batch logic. + throw new AggregateException(SR.GetResourceString(SR.ID0243), exceptions); + } + } - List? exceptions = null; + /// + public virtual async ValueTask SetApplicationIdAsync(TAuthorization authorization, + string? identifier, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - DbContextTransaction? CreateTransaction() + if (!string.IsNullOrEmpty(identifier)) + { + var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); + if (application is null) { - // Note: relational providers like Sqlite are known to lack proper support - // for repeatable read transactions. To ensure this method can be safely used - // with such providers, the database transaction is created in a try/catch block. - try - { - return Context.Database.BeginTransaction(IsolationLevel.RepeatableRead); - } - - catch - { - return null; - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0244)); } - // Note: to avoid sending too many queries, the maximum number of elements - // that can be removed by a single call to PruneAsync() is deliberately limited. - for (var index = 0; index < 1_000; index++) - { - cancellationToken.ThrowIfCancellationRequested(); - - // To prevent concurrency exceptions from being thrown if an entry is modified - // after it was retrieved from the database, the following logic is executed in - // a repeatable read transaction, that will put a lock on the retrieved entries - // and thus prevent them from being concurrently modified outside this block. - using var transaction = CreateTransaction(); - - // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable - // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. - // To work around this limitation, the threshold represented as a DateTimeOffset - // instance is manually converted to a UTC DateTime instance outside the query. - var date = threshold.UtcDateTime; - - var authorizations = - await (from authorization in Authorizations.Include(authorization => authorization.Tokens) - where authorization.CreationDate < date - where authorization.Status != Statuses.Valid || - (authorization.Type == AuthorizationTypes.AdHoc && !authorization.Tokens.Any()) - orderby authorization.Id - select authorization).Take(1_000).ToListAsync(cancellationToken); - - if (authorizations.Count == 0) - { - break; - } - - // Note: new tokens may be attached after the authorizations were retrieved - // from the database since the transaction level is deliberately limited to - // repeatable read instead of serializable for performance reasons). In this - // case, the operation will fail, which is considered an acceptable risk. - Authorizations.RemoveRange(authorizations); + authorization.Application = application; + } - try + else + { + // If the application is not attached to the authorization, try to load it manually. + if (authorization.Application is null) + { + var reference = Context.Entry(authorization).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) { - await Context.SaveChangesAsync(cancellationToken); - transaction?.Commit(); + return; } - catch (Exception exception) - { - exceptions ??= new List(capacity: 1); - exceptions.Add(exception); - } + await reference.LoadAsync(cancellationToken); } - if (exceptions is not null) - { - throw new AggregateException(SR.GetResourceString(SR.ID0243), exceptions); - } + authorization.Application = null; } + } - /// - public virtual async ValueTask SetApplicationIdAsync(TAuthorization authorization, - string? identifier, CancellationToken cancellationToken) + /// + public virtual ValueTask SetCreationDateAsync(TAuthorization authorization, + DateTimeOffset? date, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - if (!string.IsNullOrEmpty(identifier)) - { - var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0244)); - } - - authorization.Application = application; - } + throw new ArgumentNullException(nameof(authorization)); + } - else - { - // If the application is not attached to the authorization, try to load it manually. - if (authorization.Application is null) - { - var reference = Context.Entry(authorization).Reference(entry => entry.Application); - if (reference.EntityEntry.State == EntityState.Detached) - { - return; - } + authorization.CreationDate = date?.UtcDateTime; - await reference.LoadAsync(cancellationToken); - } + return default; + } - authorization.Application = null; - } + /// + public virtual ValueTask SetPropertiesAsync(TAuthorization authorization, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask SetCreationDateAsync(TAuthorization authorization, - DateTimeOffset? date, CancellationToken cancellationToken) + if (properties is null || properties.IsEmpty) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - authorization.CreationDate = date?.UtcDateTime; + authorization.Properties = null; return default; } - /// - public virtual ValueTask SetPropertiesAsync(TAuthorization authorization, - ImmutableDictionary properties, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (properties is null || properties.IsEmpty) - { - authorization.Properties = null; + writer.WriteStartObject(); - return default; - } + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartObject(); + authorization.Properties = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetScopesAsync(TAuthorization authorization, + ImmutableArray scopes, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - authorization.Properties = Encoding.UTF8.GetString(stream.ToArray()); + if (scopes.IsDefaultOrEmpty) + { + authorization.Scopes = null; return default; } - /// - public virtual ValueTask SetScopesAsync(TAuthorization authorization, - ImmutableArray scopes, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - if (scopes.IsDefaultOrEmpty) - { - authorization.Scopes = null; - - return default; - } - - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - writer.WriteStartArray(); + writer.WriteStartArray(); - foreach (var scope in scopes) - { - writer.WriteStringValue(scope); - } + foreach (var scope in scopes) + { + writer.WriteStringValue(scope); + } - writer.WriteEndArray(); - writer.Flush(); + writer.WriteEndArray(); + writer.Flush(); - authorization.Scopes = Encoding.UTF8.GetString(stream.ToArray()); + authorization.Scopes = Encoding.UTF8.GetString(stream.ToArray()); - return default; - } + return default; + } - /// - public virtual ValueTask SetStatusAsync(TAuthorization authorization, string? status, CancellationToken cancellationToken) + /// + public virtual ValueTask SetStatusAsync(TAuthorization authorization, string? status, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - authorization.Status = status; - - return default; + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask SetSubjectAsync(TAuthorization authorization, string? subject, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + authorization.Status = status; - authorization.Subject = subject; + return default; + } - return default; + /// + public virtual ValueTask SetSubjectAsync(TAuthorization authorization, string? subject, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask SetTypeAsync(TAuthorization authorization, string? type, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + authorization.Subject = subject; - authorization.Type = type; + return default; + } - return default; + /// + public virtual ValueTask SetTypeAsync(TAuthorization authorization, string? type, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual async ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + authorization.Type = type; - Authorizations.Attach(authorization); + return default; + } - // Generate a new concurrency token and attach it - // to the authorization before persisting the changes. - authorization.ConcurrencyToken = Guid.NewGuid().ToString(); + /// + public virtual async ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - Context.Entry(authorization).State = EntityState.Modified; + Authorizations.Attach(authorization); - try - { - await Context.SaveChangesAsync(cancellationToken); - } + // Generate a new concurrency token and attach it + // to the authorization before persisting the changes. + authorization.ConcurrencyToken = Guid.NewGuid().ToString(); - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(authorization).State = EntityState.Unchanged; + Context.Entry(authorization).State = EntityState.Modified; - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241), exception); - } + try + { + await Context.SaveChangesAsync(cancellationToken); } - /// - /// Converts the provided identifier to a strongly typed key object. - /// - /// The identifier to convert. - /// An instance of representing the provided identifier. - public virtual TKey? ConvertIdentifierFromString(string? identifier) + catch (DbUpdateConcurrencyException exception) { - if (string.IsNullOrEmpty(identifier)) - { - return default; - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(authorization).State = EntityState.Unchanged; - return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241), exception); } + } - /// - /// Converts the provided identifier to its string representation. - /// - /// The identifier to convert. - /// A representation of the provided identifier. - public virtual string? ConvertIdentifierToString(TKey? identifier) + /// + /// Converts the provided identifier to a strongly typed key object. + /// + /// The identifier to convert. + /// An instance of representing the provided identifier. + public virtual TKey? ConvertIdentifierFromString(string? identifier) + { + if (string.IsNullOrEmpty(identifier)) { - if (Equals(identifier, default(TKey))) - { - return null; - } + return default; + } + + return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + } - return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); + /// + /// Converts the provided identifier to its string representation. + /// + /// The identifier to convert. + /// A representation of the provided identifier. + public virtual string? ConvertIdentifierToString(TKey? identifier) + { + if (Equals(identifier, default(TKey))) + { + return null; } + + return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs index 1ce8fd99..13d80c39 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs @@ -25,708 +25,707 @@ using OpenIddict.Abstractions; using OpenIddict.EntityFramework.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Provides methods allowing to manage the scopes stored in a database. +/// +/// The type of the Entity Framework database context. +public class OpenIddictEntityFrameworkScopeStore : + OpenIddictEntityFrameworkScopeStore + where TContext : DbContext { - /// - /// Provides methods allowing to manage the scopes stored in a database. - /// - /// The type of the Entity Framework database context. - public class OpenIddictEntityFrameworkScopeStore : - OpenIddictEntityFrameworkScopeStore - where TContext : DbContext + public OpenIddictEntityFrameworkScopeStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) { - public OpenIddictEntityFrameworkScopeStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) - { - } + } +} + +/// +/// Provides methods allowing to manage the scopes stored in a database. +/// +/// The type of the Scope entity. +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkScopeStore : IOpenIddictScopeStore + where TScope : OpenIddictEntityFrameworkScope + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkScopeStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + { + Cache = cache; + Context = context; + Options = options; } /// - /// Provides methods allowing to manage the scopes stored in a database. + /// Gets the memory cache associated with the current store. /// - /// The type of the Scope entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkScopeStore : IOpenIddictScopeStore - where TScope : OpenIddictEntityFrameworkScope - where TContext : DbContext - where TKey : notnull, IEquatable - { - public OpenIddictEntityFrameworkScopeStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - { - Cache = cache; - Context = context; - Options = options; - } - - /// - /// Gets the memory cache associated with the current store. - /// - protected IMemoryCache Cache { get; } + protected IMemoryCache Cache { get; } - /// - /// Gets the database context associated with the current store. - /// - protected TContext Context { get; } + /// + /// Gets the database context associated with the current store. + /// + protected TContext Context { get; } - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Scopes => Context.Set(); + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Scopes => Context.Set(); - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) - => await Scopes.LongCountAsync(cancellationToken); + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + => await Scopes.LongCountAsync(cancellationToken); - /// - public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) + /// + public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return await query(Scopes).LongCountAsync(cancellationToken); + throw new ArgumentNullException(nameof(query)); } - /// - public virtual async ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken) + return await query(Scopes).LongCountAsync(cancellationToken); + } + + /// + public virtual async ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - Scopes.Add(scope); + Scopes.Add(scope); - await Context.SaveChangesAsync(cancellationToken); - } + await Context.SaveChangesAsync(cancellationToken); + } - /// - public virtual async ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken) + /// + public virtual async ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - Scopes.Remove(scope); + Scopes.Remove(scope); - try - { - await Context.SaveChangesAsync(cancellationToken); - } + try + { + await Context.SaveChangesAsync(cancellationToken); + } - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(scope).State = EntityState.Unchanged; + catch (DbUpdateConcurrencyException exception) + { + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(scope).State = EntityState.Unchanged; - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245), exception); - } + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245), exception); } + } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - var key = ConvertIdentifierFromString(identifier); + var key = ConvertIdentifierFromString(identifier); - return await (from scope in Scopes - where scope.Id!.Equals(key) - select scope).FirstOrDefaultAsync(cancellationToken); - } + return await (from scope in Scopes + where scope.Id!.Equals(key) + select scope).FirstOrDefaultAsync(cancellationToken); + } - /// - public virtual async ValueTask FindByNameAsync(string name, CancellationToken cancellationToken) + /// + public virtual async ValueTask FindByNameAsync(string name, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(name)) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); - } - - return await (from scope in Scopes - where scope.Name == name - select scope).FirstOrDefaultAsync(cancellationToken); + throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); } - /// - public virtual IAsyncEnumerable FindByNamesAsync( - ImmutableArray names, CancellationToken cancellationToken) - { - if (names.Any(name => string.IsNullOrEmpty(name))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); - } + return await (from scope in Scopes + where scope.Name == name + select scope).FirstOrDefaultAsync(cancellationToken); + } - // Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure - // ImmutableArray.Contains() (which is not fully supported by Entity Framework 6.x) is not used instead. - return (from scope in Scopes - where Enumerable.Contains(names, scope.Name) - select scope).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindByNamesAsync( + ImmutableArray names, CancellationToken cancellationToken) + { + if (names.Any(name => string.IsNullOrEmpty(name))) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); } - /// - public virtual IAsyncEnumerable FindByResourceAsync( - string resource, CancellationToken cancellationToken) + // Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure + // ImmutableArray.Contains() (which is not fully supported by Entity Framework 6.x) is not used instead. + return (from scope in Scopes + where Enumerable.Contains(names, scope.Name) + select scope).AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindByResourceAsync( + string resource, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(resource)) { - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); + } - // To optimize the efficiency of the query a bit, only scopes whose stringified - // Resources column contains the specified resource are returned. Once the scopes - // are retrieved, a second pass is made to ensure only valid elements are returned. - // Implementers that use this method in a hot path may want to override this method - // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. + // To optimize the efficiency of the query a bit, only scopes whose stringified + // Resources column contains the specified resource are returned. Once the scopes + // are retrieved, a second pass is made to ensure only valid elements are returned. + // Implementers that use this method in a hot path may want to override this method + // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var scopes = (from scope in Scopes - where scope.Resources!.Contains(resource) - select scope).AsAsyncEnumerable(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var scopes = (from scope in Scopes + where scope.Resources!.Contains(resource) + select scope).AsAsyncEnumerable(cancellationToken); - await foreach (var scope in scopes) + await foreach (var scope in scopes) + { + var resources = await GetResourcesAsync(scope, cancellationToken); + if (resources.Contains(resource, StringComparer.Ordinal)) { - var resources = await GetResourcesAsync(scope, cancellationToken); - if (resources.Contains(resource, StringComparer.Ordinal)) - { - yield return scope; - } + yield return scope; } } } + } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(query)); + } + + return await query(Scopes, state).FirstOrDefaultAsync(cancellationToken); + } - return await query(Scopes, state).FirstOrDefaultAsync(cancellationToken); + /// + public virtual ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken) + return new ValueTask(scope.Description); + } + + /// + public virtual ValueTask> GetDescriptionsAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - return new ValueTask(scope.Description); + if (string.IsNullOrEmpty(scope.Descriptions)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetDescriptionsAsync(TScope scope, CancellationToken cancellationToken) + // Note: parsing the stringified descriptions is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("42891062-8f69-43ba-9111-db7e8ded2553", "\x1e", scope.Descriptions); + var descriptions = Cache.GetOrCreate(key, entry => { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(scope.Descriptions)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(scope.Descriptions); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified descriptions is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("42891062-8f69-43ba-9111-db7e8ded2553", "\x1e", scope.Descriptions); - var descriptions = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(scope.Descriptions); - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var property in document.RootElement.EnumerateObject()) + var value = property.Value.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = property.Value.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder[CultureInfo.GetCultureInfo(property.Name)] = value; + continue; } - return builder.ToImmutable(); - }); + builder[CultureInfo.GetCultureInfo(property.Name)] = value; + } + + return builder.ToImmutable(); + }); + + return new ValueTask>(descriptions); + } - return new ValueTask>(descriptions); + /// + public virtual ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken) + return new ValueTask(scope.DisplayName); + } + + /// + public virtual ValueTask> GetDisplayNamesAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - return new ValueTask(scope.DisplayName); + if (string.IsNullOrEmpty(scope.DisplayNames)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetDisplayNamesAsync(TScope scope, CancellationToken cancellationToken) + // Note: parsing the stringified display names is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("e17d437b-bdd2-43f3-974e-46d524f4bae1", "\x1e", scope.DisplayNames); + var names = Cache.GetOrCreate(key, entry => { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(scope.DisplayNames)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(scope.DisplayNames); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified display names is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("e17d437b-bdd2-43f3-974e-46d524f4bae1", "\x1e", scope.DisplayNames); - var names = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(scope.DisplayNames); - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var property in document.RootElement.EnumerateObject()) + var value = property.Value.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = property.Value.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder[CultureInfo.GetCultureInfo(property.Name)] = value; + continue; } - return builder.ToImmutable(); - }); + builder[CultureInfo.GetCultureInfo(property.Name)] = value; + } - return new ValueTask>(names); - } + return builder.ToImmutable(); + }); - /// - public virtual ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken) + return new ValueTask>(names); + } + + /// + public virtual ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - return new ValueTask(ConvertIdentifierToString(scope.Id)); + return new ValueTask(ConvertIdentifierToString(scope.Id)); + } + + /// + public virtual ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken) + return new ValueTask(scope.Name); + } + + /// + public virtual ValueTask> GetPropertiesAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - return new ValueTask(scope.Name); + if (string.IsNullOrEmpty(scope.Properties)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetPropertiesAsync(TScope scope, CancellationToken cancellationToken) + // Note: parsing the stringified properties is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("78d8dfdd-3870-442e-b62e-dc9bf6eaeff7", "\x1e", scope.Properties); + var properties = Cache.GetOrCreate(key, entry => { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(scope.Properties)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(scope.Properties); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified properties is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("78d8dfdd-3870-442e-b62e-dc9bf6eaeff7", "\x1e", scope.Properties); - var properties = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); + builder[property.Name] = property.Value.Clone(); + } - using var document = JsonDocument.Parse(scope.Properties); - var builder = ImmutableDictionary.CreateBuilder(); + return builder.ToImmutable(); + }); - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return new ValueTask>(properties); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetResourcesAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - return new ValueTask>(properties); + if (string.IsNullOrEmpty(scope.Resources)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetResourcesAsync(TScope scope, CancellationToken cancellationToken) + // Note: parsing the stringified resources is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources); + var resources = Cache.GetOrCreate(key, entry => { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(scope.Resources)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(scope.Resources); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified resources is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources); - var resources = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(scope.Resources); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder.Add(value); + continue; } - return builder.ToImmutable(); - }); + builder.Add(value); + } - return new ValueTask>(resources); - } + return builder.ToImmutable(); + }); - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) - { - try - { - return new ValueTask(Activator.CreateInstance()); - } + return new ValueTask>(resources); + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0246), exception))); - } + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try + { + return new ValueTask(Activator.CreateInstance()); } - /// - public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + catch (MemberAccessException exception) { - var query = Scopes.OrderBy(scope => scope.Id!).AsQueryable(); + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0246), exception))); + } + } - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } + /// + public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + { + var query = Scopes.OrderBy(scope => scope.Id!).AsQueryable(); - if (count.HasValue) - { - query = query.Take(count.Value); - } + if (offset.HasValue) + { + query = query.Skip(offset.Value); + } - return query.AsAsyncEnumerable(cancellationToken); + if (count.HasValue) + { + query = query.Take(count.Value); } - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + return query.AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(query)); + } - return query(Scopes, state).AsAsyncEnumerable(cancellationToken); + return query(Scopes, state).AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual ValueTask SetDescriptionAsync(TScope scope, string? description, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask SetDescriptionAsync(TScope scope, string? description, CancellationToken cancellationToken) + scope.Description = description; + + return default; + } + + /// + public virtual ValueTask SetDescriptionsAsync(TScope scope, + ImmutableDictionary descriptions, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - scope.Description = description; + if (descriptions is null || descriptions.IsEmpty) + { + scope.Descriptions = null; return default; } - /// - public virtual ValueTask SetDescriptionsAsync(TScope scope, - ImmutableDictionary descriptions, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (descriptions is null || descriptions.IsEmpty) - { - scope.Descriptions = null; + writer.WriteStartObject(); - return default; - } + foreach (var description in descriptions) + { + writer.WritePropertyName(description.Key.Name); + writer.WriteStringValue(description.Value); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartObject(); + scope.Descriptions = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var description in descriptions) - { - writer.WritePropertyName(description.Key.Name); - writer.WriteStringValue(description.Value); - } + return default; + } + + /// + public virtual ValueTask SetDisplayNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - writer.WriteEndObject(); - writer.Flush(); + scope.DisplayName = name; - scope.Descriptions = Encoding.UTF8.GetString(stream.ToArray()); + return default; + } - return default; + /// + public virtual ValueTask SetDisplayNamesAsync(TScope scope, + ImmutableDictionary names, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask SetDisplayNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + if (names is null || names.IsEmpty) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - scope.DisplayName = name; + scope.DisplayNames = null; return default; } - /// - public virtual ValueTask SetDisplayNamesAsync(TScope scope, - ImmutableDictionary names, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (names is null || names.IsEmpty) - { - scope.DisplayNames = null; + writer.WriteStartObject(); - return default; - } + foreach (var name in names) + { + writer.WritePropertyName(name.Key.Name); + writer.WriteStringValue(name.Value); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartObject(); + scope.DisplayNames = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var name in names) - { - writer.WritePropertyName(name.Key.Name); - writer.WriteStringValue(name.Value); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - scope.DisplayNames = Encoding.UTF8.GetString(stream.ToArray()); + scope.Name = name; - return default; - } + return default; + } - /// - public virtual ValueTask SetNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + /// + public virtual ValueTask SetPropertiesAsync(TScope scope, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - scope.Name = name; + if (properties is null || properties.IsEmpty) + { + scope.Properties = null; return default; } - /// - public virtual ValueTask SetPropertiesAsync(TScope scope, - ImmutableDictionary properties, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (properties is null || properties.IsEmpty) - { - scope.Properties = null; + writer.WriteStartObject(); - return default; - } + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartObject(); + scope.Properties = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetResourcesAsync(TScope scope, ImmutableArray resources, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - scope.Properties = Encoding.UTF8.GetString(stream.ToArray()); + if (resources.IsDefaultOrEmpty) + { + scope.Resources = null; return default; } - /// - public virtual ValueTask SetResourcesAsync(TScope scope, ImmutableArray resources, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (resources.IsDefaultOrEmpty) - { - scope.Resources = null; - - return default; - } + writer.WriteStartArray(); - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); - - writer.WriteStartArray(); - - foreach (var resource in resources) - { - writer.WriteStringValue(resource); - } + foreach (var resource in resources) + { + writer.WriteStringValue(resource); + } - writer.WriteEndArray(); - writer.Flush(); + writer.WriteEndArray(); + writer.Flush(); - scope.Resources = Encoding.UTF8.GetString(stream.ToArray()); + scope.Resources = Encoding.UTF8.GetString(stream.ToArray()); - return default; - } + return default; + } - /// - public virtual async ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken) + /// + public virtual async ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - Scopes.Attach(scope); + Scopes.Attach(scope); - // Generate a new concurrency token and attach it - // to the scope before persisting the changes. - scope.ConcurrencyToken = Guid.NewGuid().ToString(); + // Generate a new concurrency token and attach it + // to the scope before persisting the changes. + scope.ConcurrencyToken = Guid.NewGuid().ToString(); - Context.Entry(scope).State = EntityState.Modified; + Context.Entry(scope).State = EntityState.Modified; - try - { - await Context.SaveChangesAsync(cancellationToken); - } - - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(scope).State = EntityState.Unchanged; - - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245), exception); - } + try + { + await Context.SaveChangesAsync(cancellationToken); } - /// - /// Converts the provided identifier to a strongly typed key object. - /// - /// The identifier to convert. - /// An instance of representing the provided identifier. - public virtual TKey? ConvertIdentifierFromString(string? identifier) + catch (DbUpdateConcurrencyException exception) { - if (string.IsNullOrEmpty(identifier)) - { - return default; - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(scope).State = EntityState.Unchanged; - return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245), exception); } + } - /// - /// Converts the provided identifier to its string representation. - /// - /// The identifier to convert. - /// A representation of the provided identifier. - public virtual string? ConvertIdentifierToString(TKey? identifier) + /// + /// Converts the provided identifier to a strongly typed key object. + /// + /// The identifier to convert. + /// An instance of representing the provided identifier. + public virtual TKey? ConvertIdentifierFromString(string? identifier) + { + if (string.IsNullOrEmpty(identifier)) { - if (Equals(identifier, default(TKey))) - { - return null; - } + return default; + } + + return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + } - return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); + /// + /// Converts the provided identifier to its string representation. + /// + /// The identifier to convert. + /// A representation of the provided identifier. + public virtual string? ConvertIdentifierToString(TKey? identifier) + { + if (Equals(identifier, default(TKey))) + { + return null; } + + return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs index 24747359..edf430c3 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs @@ -25,915 +25,914 @@ using OpenIddict.EntityFramework.Models; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework +namespace OpenIddict.EntityFramework; + +/// +/// Provides methods allowing to manage the tokens stored in a database. +/// +/// The type of the Entity Framework database context. +public class OpenIddictEntityFrameworkTokenStore : + OpenIddictEntityFrameworkTokenStore + where TContext : DbContext { + public OpenIddictEntityFrameworkTokenStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) + { + } +} + +/// +/// Provides methods allowing to manage the tokens stored in a database. +/// +/// The type of the Token entity. +/// The type of the Application entity. +/// The type of the Authorization entity. +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkTokenStore : IOpenIddictTokenStore + where TToken : OpenIddictEntityFrameworkToken + where TApplication : OpenIddictEntityFrameworkApplication + where TAuthorization : OpenIddictEntityFrameworkAuthorization + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkTokenStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + { + Cache = cache; + Context = context; + Options = options; + } + + /// + /// Gets the memory cache associated with the current store. + /// + protected IMemoryCache Cache { get; } + /// - /// Provides methods allowing to manage the tokens stored in a database. + /// Gets the database context associated with the current store. /// - /// The type of the Entity Framework database context. - public class OpenIddictEntityFrameworkTokenStore : - OpenIddictEntityFrameworkTokenStore - where TContext : DbContext + protected TContext Context { get; } + + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Applications => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Authorizations => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Tokens => Context.Set(); + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + => await Tokens.LongCountAsync(cancellationToken); + + /// + public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkTokenStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) + if (query is null) { + throw new ArgumentNullException(nameof(query)); } - } - /// - /// Provides methods allowing to manage the tokens stored in a database. - /// - /// The type of the Token entity. - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkTokenStore : IOpenIddictTokenStore - where TToken : OpenIddictEntityFrameworkToken - where TApplication : OpenIddictEntityFrameworkApplication - where TAuthorization : OpenIddictEntityFrameworkAuthorization - where TContext : DbContext - where TKey : notnull, IEquatable - { - public OpenIddictEntityFrameworkTokenStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - { - Cache = cache; - Context = context; - Options = options; - } - - /// - /// Gets the memory cache associated with the current store. - /// - protected IMemoryCache Cache { get; } - - /// - /// Gets the database context associated with the current store. - /// - protected TContext Context { get; } - - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Applications => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Authorizations => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Tokens => Context.Set(); - - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) - => await Tokens.LongCountAsync(cancellationToken); - - /// - public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + return await query(Tokens).LongCountAsync(cancellationToken); + } - return await query(Tokens).LongCountAsync(cancellationToken); + /// + public virtual async ValueTask CreateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask CreateAsync(TToken token, CancellationToken cancellationToken) + Tokens.Add(token); + + await Context.SaveChangesAsync(cancellationToken); + } + + /// + public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - Tokens.Add(token); + Tokens.Remove(token); + try + { await Context.SaveChangesAsync(cancellationToken); } - /// - public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken) + catch (DbUpdateConcurrencyException exception) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - Tokens.Remove(token); - - try - { - await Context.SaveChangesAsync(cancellationToken); - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(token).State = EntityState.Unchanged; - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(token).State = EntityState.Unchanged; + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247), exception); + } + } - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247), exception); - } + /// + public virtual IAsyncEnumerable FindAsync(string subject, + string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync(string subject, - string client, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) + where token.Application!.Id!.Equals(key) && + token.Subject == subject + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Application!.Id!.Equals(key) && - token.Subject == subject - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) + where token.Application!.Id!.Equals(key) && + token.Subject == subject && + token.Status == status + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Application!.Id!.Equals(key) && - token.Subject == subject && - token.Status == status - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) + where token.Application!.Id!.Equals(key) && + token.Subject == subject && + token.Status == status && + token.Type == type + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Application!.Id!.Equals(key) && - token.Subject == subject && - token.Status == status && - token.Type == type - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + var key = ConvertIdentifierFromString(identifier); - var key = ConvertIdentifierFromString(identifier); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) + where token.Application!.Id!.Equals(key) + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Application!.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + var key = ConvertIdentifierFromString(identifier); - var key = ConvertIdentifierFromString(identifier); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) + where token.Authorization!.Id!.Equals(key) + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Authorization!.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + var key = ConvertIdentifierFromString(identifier); - var key = ConvertIdentifierFromString(identifier); + return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) + where token.Id!.Equals(key) + select token).FirstOrDefaultAsync(cancellationToken); + } - return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Id!.Equals(key) - select token).FirstOrDefaultAsync(cancellationToken); + /// + public virtual async ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual async ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) + return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) + where token.ReferenceId == identifier + select token).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.ReferenceId == identifier - select token).FirstOrDefaultAsync(cancellationToken); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) + where token.Subject == subject + select token).AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual async ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) + // If the application is not attached to the token, try to load it manually. + if (token.Application is null) { - if (string.IsNullOrEmpty(subject)) + var reference = Context.Entry(token).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + return null; } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization) - where token.Subject == subject - select token).AsAsyncEnumerable(cancellationToken); + await reference.LoadAsync(cancellationToken); } - /// - public virtual async ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken) + if (token.Application is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return null; + } - // If the application is not attached to the token, try to load it manually. - if (token.Application is null) - { - var reference = Context.Entry(token).Reference(entry => entry.Application); - if (reference.EntityEntry.State == EntityState.Detached) - { - return null; - } + return ConvertIdentifierToString(token.Application.Id); + } - await reference.LoadAsync(cancellationToken); - } + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } - if (token.Application is null) - { - return null; - } + return await query( + Tokens.Include(token => token.Application) + .Include(token => token.Authorization), state).FirstOrDefaultAsync(cancellationToken); + } - return ConvertIdentifierToString(token.Application.Id); + /// + public virtual async ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + // If the authorization is not attached to the token, try to load it manually. + if (token.Authorization is null) { - if (query is null) + var reference = Context.Entry(token).Reference(entry => entry.Authorization); + if (reference.EntityEntry.State == EntityState.Detached) { - throw new ArgumentNullException(nameof(query)); + return null; } - return await query( - Tokens.Include(token => token.Application) - .Include(token => token.Authorization), state).FirstOrDefaultAsync(cancellationToken); + await reference.LoadAsync(cancellationToken); } - /// - public virtual async ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken) + if (token.Authorization is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - // If the authorization is not attached to the token, try to load it manually. - if (token.Authorization is null) - { - var reference = Context.Entry(token).Reference(entry => entry.Authorization); - if (reference.EntityEntry.State == EntityState.Detached) - { - return null; - } - - await reference.LoadAsync(cancellationToken); - } + return null; + } - if (token.Authorization is null) - { - return null; - } + return ConvertIdentifierToString(token.Authorization.Id); + } - return ConvertIdentifierToString(token.Authorization.Id); + /// + public virtual ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken) + if (token.CreationDate is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(result: null); + } - if (token.CreationDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc)); + } - return new ValueTask(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken) + if (token.ExpirationDate is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(result: null); + } - if (token.ExpirationDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc)); + } - return new ValueTask(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(ConvertIdentifierToString(token.Id)); + } - return new ValueTask(ConvertIdentifierToString(token.Id)); + /// + public virtual ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken) + return new ValueTask(token.Payload); + } + + /// + public virtual ValueTask> GetPropertiesAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - return new ValueTask(token.Payload); + if (string.IsNullOrEmpty(token.Properties)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetPropertiesAsync(TToken token, CancellationToken cancellationToken) + // Note: parsing the stringified properties is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("d0509397-1bbf-40e7-97e1-5e6d7bc2536c", "\x1e", token.Properties); + var properties = Cache.GetOrCreate(key, entry => { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(token.Properties)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(token.Properties); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified properties is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("d0509397-1bbf-40e7-97e1-5e6d7bc2536c", "\x1e", token.Properties); - var properties = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(token.Properties); - var builder = ImmutableDictionary.CreateBuilder(); + builder[property.Name] = property.Value.Clone(); + } - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return builder.ToImmutable(); + }); - return builder.ToImmutable(); - }); + return new ValueTask>(properties); + } - return new ValueTask>(properties); + /// + public virtual ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken) + if (token.RedemptionDate is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(result: null); + } - if (token.RedemptionDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc)); + } - return new ValueTask(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(token.ReferenceId); + } - return new ValueTask(token.ReferenceId); + /// + public virtual ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(token.Status); + } - return new ValueTask(token.Status); + /// + public virtual ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(token.Subject); + } - return new ValueTask(token.Subject); + /// + public virtual ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken) + return new ValueTask(token.Type); + } + + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(Activator.CreateInstance()); + } - return new ValueTask(token.Type); + catch (MemberAccessException exception) + { + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0248), exception))); } + } - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + { + var query = Tokens.Include(token => token.Application) + .Include(token => token.Authorization) + .OrderBy(token => token.Id!) + .AsQueryable(); + + if (offset.HasValue) { - try - { - return new ValueTask(Activator.CreateInstance()); - } + query = query.Skip(offset.Value); + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0248), exception))); - } + if (count.HasValue) + { + query = query.Take(count.Value); } - /// - public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + return query.AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - var query = Tokens.Include(token => token.Application) - .Include(token => token.Authorization) - .OrderBy(token => token.Id!) - .AsQueryable(); + throw new ArgumentNullException(nameof(query)); + } - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } + return query( + Tokens.Include(token => token.Application) + .Include(token => token.Authorization), state).AsAsyncEnumerable(cancellationToken); + } - if (count.HasValue) - { - query = query.Take(count.Value); - } + /// + public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + { + // Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing + // entities in a single command without having to retrieve and materialize them first. + // To work around this limitation, entities are manually listed and deleted using a batch logic. - return query.AsAsyncEnumerable(cancellationToken); - } + List? exceptions = null; - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + DbContextTransaction? CreateTransaction() { - if (query is null) + // Note: relational providers like Sqlite are known to lack proper support + // for repeatable read transactions. To ensure this method can be safely used + // with such providers, the database transaction is created in a try/catch block. + try { - throw new ArgumentNullException(nameof(query)); + return Context.Database.BeginTransaction(IsolationLevel.RepeatableRead); } - return query( - Tokens.Include(token => token.Application) - .Include(token => token.Authorization), state).AsAsyncEnumerable(cancellationToken); + catch + { + return null; + } } - /// - public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + // Note: to avoid sending too many queries, the maximum number of elements + // that can be removed by a single call to PruneAsync() is deliberately limited. + for (var index = 0; index < 1_000; index++) { - // Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing - // entities in a single command without having to retrieve and materialize them first. - // To work around this limitation, entities are manually listed and deleted using a batch logic. + cancellationToken.ThrowIfCancellationRequested(); - List? exceptions = null; + // To prevent concurrency exceptions from being thrown if an entry is modified + // after it was retrieved from the database, the following logic is executed in + // a repeatable read transaction, that will put a lock on the retrieved entries + // and thus prevent them from being concurrently modified outside this block. + using var transaction = CreateTransaction(); - DbContextTransaction? CreateTransaction() - { - // Note: relational providers like Sqlite are known to lack proper support - // for repeatable read transactions. To ensure this method can be safely used - // with such providers, the database transaction is created in a try/catch block. - try - { - return Context.Database.BeginTransaction(IsolationLevel.RepeatableRead); - } + // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable + // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. + // To work around this limitation, the threshold represented as a DateTimeOffset + // instance is manually converted to a UTC DateTime instance outside the query. + var date = threshold.UtcDateTime; - catch - { - return null; - } - } + var tokens = await + (from token in Tokens + where token.CreationDate < date + where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || + (token.Authorization != null && token.Authorization.Status != Statuses.Valid) || + token.ExpirationDate < DateTime.UtcNow + orderby token.Id + select token).Take(1_000).ToListAsync(cancellationToken); - // Note: to avoid sending too many queries, the maximum number of elements - // that can be removed by a single call to PruneAsync() is deliberately limited. - for (var index = 0; index < 1_000; index++) + if (tokens.Count == 0) { - cancellationToken.ThrowIfCancellationRequested(); - - // To prevent concurrency exceptions from being thrown if an entry is modified - // after it was retrieved from the database, the following logic is executed in - // a repeatable read transaction, that will put a lock on the retrieved entries - // and thus prevent them from being concurrently modified outside this block. - using var transaction = CreateTransaction(); - - // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable - // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. - // To work around this limitation, the threshold represented as a DateTimeOffset - // instance is manually converted to a UTC DateTime instance outside the query. - var date = threshold.UtcDateTime; - - var tokens = await - (from token in Tokens - where token.CreationDate < date - where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || - (token.Authorization != null && token.Authorization.Status != Statuses.Valid) || - token.ExpirationDate < DateTime.UtcNow - orderby token.Id - select token).Take(1_000).ToListAsync(cancellationToken); - - if (tokens.Count == 0) - { - break; - } - - Tokens.RemoveRange(tokens); - - try - { - await Context.SaveChangesAsync(cancellationToken); - transaction?.Commit(); - } - - catch (Exception exception) - { - exceptions ??= new List(capacity: 1); - exceptions.Add(exception); - } + break; } - if (exceptions is not null) - { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); - } - } + Tokens.RemoveRange(tokens); - /// - public virtual async ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) - { - if (token is null) + try { - throw new ArgumentNullException(nameof(token)); + await Context.SaveChangesAsync(cancellationToken); + transaction?.Commit(); } - if (!string.IsNullOrEmpty(identifier)) + catch (Exception exception) { - var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0250)); - } - - token.Application = application; + exceptions ??= new List(capacity: 1); + exceptions.Add(exception); } + } - else - { - // If the application is not attached to the token, try to load it manually. - if (token.Application is null) - { - var reference = Context.Entry(token).Reference(entry => entry.Application); - if (reference.EntityEntry.State == EntityState.Detached) - { - return; - } - - await reference.LoadAsync(cancellationToken); - } + if (exceptions is not null) + { + throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); + } + } - token.Application = null; - } + /// + public virtual async ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask SetAuthorizationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + if (!string.IsNullOrEmpty(identifier)) { - if (token is null) + var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); + if (application is null) { - throw new ArgumentNullException(nameof(token)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0250)); } - if (!string.IsNullOrEmpty(identifier)) + token.Application = application; + } + + else + { + // If the application is not attached to the token, try to load it manually. + if (token.Application is null) { - var authorization = await Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); - if (authorization is null) + var reference = Context.Entry(token).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0251)); + return; } - token.Authorization = authorization; + await reference.LoadAsync(cancellationToken); } - else - { - // If the authorization is not attached to the token, try to load it manually. - if (token.Authorization is null) - { - var reference = Context.Entry(token).Reference(entry => entry.Authorization); - if (reference.EntityEntry.State == EntityState.Detached) - { - return; - } - - await reference.LoadAsync(cancellationToken); - } + token.Application = null; + } + } - token.Authorization = null; - } + /// + public virtual async ValueTask SetAuthorizationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask SetCreationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + if (!string.IsNullOrEmpty(identifier)) { - if (token is null) + var authorization = await Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier)); + if (authorization is null) { - throw new ArgumentNullException(nameof(token)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0251)); } - token.CreationDate = date?.UtcDateTime; - - return default; + token.Authorization = authorization; } - /// - public virtual ValueTask SetExpirationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + else { - if (token is null) + // If the authorization is not attached to the token, try to load it manually. + if (token.Authorization is null) { - throw new ArgumentNullException(nameof(token)); - } + var reference = Context.Entry(token).Reference(entry => entry.Authorization); + if (reference.EntityEntry.State == EntityState.Detached) + { + return; + } - token.ExpirationDate = date?.UtcDateTime; + await reference.LoadAsync(cancellationToken); + } - return default; + token.Authorization = null; } + } - /// - public virtual ValueTask SetPayloadAsync(TToken token, string? payload, CancellationToken cancellationToken) + /// + public virtual ValueTask SetCreationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - token.Payload = payload; + token.CreationDate = date?.UtcDateTime; - return default; - } + return default; + } - /// - public virtual ValueTask SetPropertiesAsync(TToken token, - ImmutableDictionary properties, CancellationToken cancellationToken) + /// + public virtual ValueTask SetExpirationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - if (properties is null || properties.IsEmpty) - { - token.Properties = null; + token.ExpirationDate = date?.UtcDateTime; - return default; - } + return default; + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + /// + public virtual ValueTask SetPayloadAsync(TToken token, string? payload, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - writer.WriteStartObject(); + token.Payload = payload; - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetPropertiesAsync(TToken token, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - token.Properties = Encoding.UTF8.GetString(stream.ToArray()); + if (properties is null || properties.IsEmpty) + { + token.Properties = null; return default; } - /// - public virtual ValueTask SetRedemptionDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - token.RedemptionDate = date?.UtcDateTime; + writer.WriteStartObject(); - return default; + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); } - /// - public virtual ValueTask SetReferenceIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + writer.WriteEndObject(); + writer.Flush(); - token.ReferenceId = identifier; + token.Properties = Encoding.UTF8.GetString(stream.ToArray()); - return default; - } + return default; + } - /// - public virtual ValueTask SetStatusAsync(TToken token, string? status, CancellationToken cancellationToken) + /// + public virtual ValueTask SetRedemptionDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - token.Status = status; + token.RedemptionDate = date?.UtcDateTime; - return default; - } + return default; + } - /// - public virtual ValueTask SetSubjectAsync(TToken token, string? subject, CancellationToken cancellationToken) + /// + public virtual ValueTask SetReferenceIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - token.Subject = subject; + token.ReferenceId = identifier; - return default; - } + return default; + } - /// - public virtual ValueTask SetTypeAsync(TToken token, string? type, CancellationToken cancellationToken) + /// + public virtual ValueTask SetStatusAsync(TToken token, string? status, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - token.Type = type; + token.Status = status; - return default; + return default; + } + + /// + public virtual ValueTask SetSubjectAsync(TToken token, string? subject, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken) + token.Subject = subject; + + return default; + } + + /// + public virtual ValueTask SetTypeAsync(TToken token, string? type, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - Tokens.Attach(token); + token.Type = type; - // Generate a new concurrency token and attach it - // to the token before persisting the changes. - token.ConcurrencyToken = Guid.NewGuid().ToString(); + return default; + } - Context.Entry(token).State = EntityState.Modified; + /// + public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - try - { - await Context.SaveChangesAsync(cancellationToken); - } + Tokens.Attach(token); - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(token).State = EntityState.Unchanged; + // Generate a new concurrency token and attach it + // to the token before persisting the changes. + token.ConcurrencyToken = Guid.NewGuid().ToString(); - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247), exception); - } + Context.Entry(token).State = EntityState.Modified; + + try + { + await Context.SaveChangesAsync(cancellationToken); } - /// - /// Converts the provided identifier to a strongly typed key object. - /// - /// The identifier to convert. - /// An instance of representing the provided identifier. - public virtual TKey? ConvertIdentifierFromString(string? identifier) + catch (DbUpdateConcurrencyException exception) { - if (string.IsNullOrEmpty(identifier)) - { - return default; - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(token).State = EntityState.Unchanged; - return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247), exception); } + } - /// - /// Converts the provided identifier to its string representation. - /// - /// The identifier to convert. - /// A representation of the provided identifier. - public virtual string? ConvertIdentifierToString(TKey? identifier) + /// + /// Converts the provided identifier to a strongly typed key object. + /// + /// The identifier to convert. + /// An instance of representing the provided identifier. + public virtual TKey? ConvertIdentifierFromString(string? identifier) + { + if (string.IsNullOrEmpty(identifier)) { - if (Equals(identifier, default(TKey))) - { - return null; - } + return default; + } - return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); + return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + } + + /// + /// Converts the provided identifier to its string representation. + /// + /// The identifier to convert. + /// A representation of the provided identifier. + public virtual string? ConvertIdentifierToString(TKey? identifier) + { + if (Equals(identifier, default(TKey))) + { + return null; } + + return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs index 79dfb24b..fcbd9ad0 100644 --- a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs +++ b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs @@ -8,119 +8,118 @@ using System; using System.Collections.Generic; using System.Diagnostics; -namespace OpenIddict.EntityFrameworkCore.Models +namespace OpenIddict.EntityFrameworkCore.Models; + +/// +/// Represents an OpenIddict application. +/// +public class OpenIddictEntityFrameworkCoreApplication : OpenIddictEntityFrameworkCoreApplication { - /// - /// Represents an OpenIddict application. - /// - public class OpenIddictEntityFrameworkCoreApplication : OpenIddictEntityFrameworkCoreApplication + public OpenIddictEntityFrameworkCoreApplication() { - public OpenIddictEntityFrameworkCoreApplication() - { - // Generate a new string identifier. - Id = Guid.NewGuid().ToString(); - } + // Generate a new string identifier. + Id = Guid.NewGuid().ToString(); } +} +/// +/// Represents an OpenIddict application. +/// +public class OpenIddictEntityFrameworkCoreApplication : OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreToken> + where TKey : notnull, IEquatable +{ +} + +/// +/// Represents an OpenIddict application. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; ClientId = {ClientId,nq} ; Type = {Type,nq}")] +public class OpenIddictEntityFrameworkCoreApplication + where TKey : notnull, IEquatable + where TAuthorization : class + where TToken : class +{ /// - /// Represents an OpenIddict application. + /// Gets the list of the authorizations associated with this application. /// - public class OpenIddictEntityFrameworkCoreApplication : OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreToken> - where TKey : notnull, IEquatable - { - } + public virtual ICollection Authorizations { get; } = new HashSet(); /// - /// Represents an OpenIddict application. + /// Gets or sets the client identifier associated with the current application. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; ClientId = {ClientId,nq} ; Type = {Type,nq}")] - public class OpenIddictEntityFrameworkCoreApplication - where TKey : notnull, IEquatable - where TAuthorization : class - where TToken : class - { - /// - /// Gets the list of the authorizations associated with this application. - /// - public virtual ICollection Authorizations { get; } = new HashSet(); - - /// - /// Gets or sets the client identifier associated with the current application. - /// - public virtual string? ClientId { get; set; } - - /// - /// Gets or sets the client secret associated with the current application. - /// Note: depending on the application manager used to create this instance, - /// this property may be hashed or encrypted for security reasons. - /// - public virtual string? ClientSecret { get; set; } - - /// - /// Gets or sets the concurrency token. - /// - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - - /// - /// Gets or sets the consent type associated with the current application. - /// - public virtual string? ConsentType { get; set; } - - /// - /// Gets or sets the display name associated with the current application. - /// - public virtual string? DisplayName { get; set; } - - /// - /// Gets or sets the localized display names - /// associated with the current application, - /// serialized as a JSON object. - /// - public virtual string? DisplayNames { get; set; } - - /// - /// Gets or sets the unique identifier associated with the current application. - /// - public virtual TKey? Id { get; set; } - - /// - /// Gets or sets the permissions associated with the - /// current application, serialized as a JSON array. - /// - public virtual string? Permissions { get; set; } - - /// - /// Gets or sets the logout callback URLs associated with - /// the current application, serialized as a JSON array. - /// - public virtual string? PostLogoutRedirectUris { get; set; } - - /// - /// Gets or sets the additional properties serialized as a JSON object, - /// or null if no bag was associated with the current application. - /// - public virtual string? Properties { get; set; } - - /// - /// Gets or sets the callback URLs associated with the - /// current application, serialized as a JSON array. - /// - public virtual string? RedirectUris { get; set; } - - /// - /// Gets or sets the requirements associated with the - /// current application, serialized as a JSON array. - /// - public virtual string? Requirements { get; set; } - - /// - /// Gets the list of the tokens associated with this application. - /// - public virtual ICollection Tokens { get; } = new HashSet(); - - /// - /// Gets or sets the application type associated with the current application. - /// - public virtual string? Type { get; set; } - } -} \ No newline at end of file + public virtual string? ClientId { get; set; } + + /// + /// Gets or sets the client secret associated with the current application. + /// Note: depending on the application manager used to create this instance, + /// this property may be hashed or encrypted for security reasons. + /// + public virtual string? ClientSecret { get; set; } + + /// + /// Gets or sets the concurrency token. + /// + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + + /// + /// Gets or sets the consent type associated with the current application. + /// + public virtual string? ConsentType { get; set; } + + /// + /// Gets or sets the display name associated with the current application. + /// + public virtual string? DisplayName { get; set; } + + /// + /// Gets or sets the localized display names + /// associated with the current application, + /// serialized as a JSON object. + /// + public virtual string? DisplayNames { get; set; } + + /// + /// Gets or sets the unique identifier associated with the current application. + /// + public virtual TKey? Id { get; set; } + + /// + /// Gets or sets the permissions associated with the + /// current application, serialized as a JSON array. + /// + public virtual string? Permissions { get; set; } + + /// + /// Gets or sets the logout callback URLs associated with + /// the current application, serialized as a JSON array. + /// + public virtual string? PostLogoutRedirectUris { get; set; } + + /// + /// Gets or sets the additional properties serialized as a JSON object, + /// or null if no bag was associated with the current application. + /// + public virtual string? Properties { get; set; } + + /// + /// Gets or sets the callback URLs associated with the + /// current application, serialized as a JSON array. + /// + public virtual string? RedirectUris { get; set; } + + /// + /// Gets or sets the requirements associated with the + /// current application, serialized as a JSON array. + /// + public virtual string? Requirements { get; set; } + + /// + /// Gets the list of the tokens associated with this application. + /// + public virtual ICollection Tokens { get; } = new HashSet(); + + /// + /// Gets or sets the application type associated with the current application. + /// + public virtual string? Type { get; set; } +} diff --git a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs index 7eca42c4..fbe4e1f3 100644 --- a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs +++ b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs @@ -8,87 +8,86 @@ using System; using System.Collections.Generic; using System.Diagnostics; -namespace OpenIddict.EntityFrameworkCore.Models +namespace OpenIddict.EntityFrameworkCore.Models; + +/// +/// Represents an OpenIddict authorization. +/// +public class OpenIddictEntityFrameworkCoreAuthorization : OpenIddictEntityFrameworkCoreAuthorization { - /// - /// Represents an OpenIddict authorization. - /// - public class OpenIddictEntityFrameworkCoreAuthorization : OpenIddictEntityFrameworkCoreAuthorization + public OpenIddictEntityFrameworkCoreAuthorization() { - public OpenIddictEntityFrameworkCoreAuthorization() - { - // Generate a new string identifier. - Id = Guid.NewGuid().ToString(); - } + // Generate a new string identifier. + Id = Guid.NewGuid().ToString(); } +} +/// +/// Represents an OpenIddict authorization. +/// +public class OpenIddictEntityFrameworkCoreAuthorization : OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreToken> + where TKey : notnull, IEquatable +{ +} + +/// +/// Represents an OpenIddict authorization. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] +public class OpenIddictEntityFrameworkCoreAuthorization + where TKey : notnull, IEquatable + where TApplication : class + where TToken : class +{ /// - /// Represents an OpenIddict authorization. + /// Gets or sets the application associated with the current authorization. /// - public class OpenIddictEntityFrameworkCoreAuthorization : OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreToken> - where TKey : notnull, IEquatable - { - } + public virtual TApplication? Application { get; set; } /// - /// Represents an OpenIddict authorization. + /// Gets or sets the concurrency token. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] - public class OpenIddictEntityFrameworkCoreAuthorization - where TKey : notnull, IEquatable - where TApplication : class - where TToken : class - { - /// - /// Gets or sets the application associated with the current authorization. - /// - public virtual TApplication? Application { get; set; } - - /// - /// Gets or sets the concurrency token. - /// - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - /// - /// Gets or sets the UTC creation date of the current authorization. - /// - public virtual DateTime? CreationDate { get; set; } + /// + /// Gets or sets the UTC creation date of the current authorization. + /// + public virtual DateTime? CreationDate { get; set; } - /// - /// Gets or sets the unique identifier associated with the current authorization. - /// - public virtual TKey? Id { get; set; } + /// + /// Gets or sets the unique identifier associated with the current authorization. + /// + public virtual TKey? Id { get; set; } - /// - /// Gets or sets the additional properties serialized as a JSON object, - /// or null if no bag was associated with the current authorization. - /// - public virtual string? Properties { get; set; } + /// + /// Gets or sets the additional properties serialized as a JSON object, + /// or null if no bag was associated with the current authorization. + /// + public virtual string? Properties { get; set; } - /// - /// Gets or sets the scopes associated with the current - /// authorization, serialized as a JSON array. - /// - public virtual string? Scopes { get; set; } + /// + /// Gets or sets the scopes associated with the current + /// authorization, serialized as a JSON array. + /// + public virtual string? Scopes { get; set; } - /// - /// Gets or sets the status of the current authorization. - /// - public virtual string? Status { get; set; } + /// + /// Gets or sets the status of the current authorization. + /// + public virtual string? Status { get; set; } - /// - /// Gets or sets the subject associated with the current authorization. - /// - public virtual string? Subject { get; set; } + /// + /// Gets or sets the subject associated with the current authorization. + /// + public virtual string? Subject { get; set; } - /// - /// Gets the list of tokens associated with the current authorization. - /// - public virtual ICollection Tokens { get; } = new HashSet(); + /// + /// Gets the list of tokens associated with the current authorization. + /// + public virtual ICollection Tokens { get; } = new HashSet(); - /// - /// Gets or sets the type of the current authorization. - /// - public virtual string? Type { get; set; } - } + /// + /// Gets or sets the type of the current authorization. + /// + public virtual string? Type { get; set; } } diff --git a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs index 329ab05e..a12bfeff 100644 --- a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs +++ b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreScope.cs @@ -7,74 +7,73 @@ using System; using System.Diagnostics; -namespace OpenIddict.EntityFrameworkCore.Models +namespace OpenIddict.EntityFrameworkCore.Models; + +/// +/// Represents an OpenIddict scope. +/// +public class OpenIddictEntityFrameworkCoreScope : OpenIddictEntityFrameworkCoreScope { - /// - /// Represents an OpenIddict scope. - /// - public class OpenIddictEntityFrameworkCoreScope : OpenIddictEntityFrameworkCoreScope + public OpenIddictEntityFrameworkCoreScope() { - public OpenIddictEntityFrameworkCoreScope() - { - // Generate a new string identifier. - Id = Guid.NewGuid().ToString(); - } + // Generate a new string identifier. + Id = Guid.NewGuid().ToString(); } +} +/// +/// Represents an OpenIddict scope. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; Name = {Name,nq}")] +public class OpenIddictEntityFrameworkCoreScope where TKey : notnull, IEquatable +{ /// - /// Represents an OpenIddict scope. + /// Gets or sets the concurrency token. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; Name = {Name,nq}")] - public class OpenIddictEntityFrameworkCoreScope where TKey : notnull, IEquatable - { - /// - /// Gets or sets the concurrency token. - /// - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - /// - /// Gets or sets the public description associated with the current scope. - /// - public virtual string? Description { get; set; } + /// + /// Gets or sets the public description associated with the current scope. + /// + public virtual string? Description { get; set; } - /// - /// Gets or sets the localized public descriptions associated - /// with the current scope, serialized as a JSON object. - /// - public virtual string? Descriptions { get; set; } + /// + /// Gets or sets the localized public descriptions associated + /// with the current scope, serialized as a JSON object. + /// + public virtual string? Descriptions { get; set; } - /// - /// Gets or sets the display name associated with the current scope. - /// - public virtual string? DisplayName { get; set; } + /// + /// Gets or sets the display name associated with the current scope. + /// + public virtual string? DisplayName { get; set; } - /// - /// Gets or sets the localized display names - /// associated with the current application, - /// serialized as a JSON object. - /// - public virtual string? DisplayNames { get; set; } + /// + /// Gets or sets the localized display names + /// associated with the current application, + /// serialized as a JSON object. + /// + public virtual string? DisplayNames { get; set; } - /// - /// Gets or sets the unique identifier associated with the current scope. - /// - public virtual TKey? Id { get; set; } + /// + /// Gets or sets the unique identifier associated with the current scope. + /// + public virtual TKey? Id { get; set; } - /// - /// Gets or sets the unique name associated with the current scope. - /// - public virtual string? Name { get; set; } + /// + /// Gets or sets the unique name associated with the current scope. + /// + public virtual string? Name { get; set; } - /// - /// Gets or sets the additional properties serialized as a JSON object, - /// or null if no bag was associated with the current scope. - /// - public virtual string? Properties { get; set; } + /// + /// Gets or sets the additional properties serialized as a JSON object, + /// or null if no bag was associated with the current scope. + /// + public virtual string? Properties { get; set; } - /// - /// Gets or sets the resources associated with the - /// current scope, serialized as a JSON array. - /// - public virtual string? Resources { get; set; } - } + /// + /// Gets or sets the resources associated with the + /// current scope, serialized as a JSON array. + /// + public virtual string? Resources { get; set; } } diff --git a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs index 68cb650a..586df07d 100644 --- a/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs +++ b/src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs @@ -7,106 +7,105 @@ using System; using System.Diagnostics; -namespace OpenIddict.EntityFrameworkCore.Models +namespace OpenIddict.EntityFrameworkCore.Models; + +/// +/// Represents an OpenIddict token. +/// +public class OpenIddictEntityFrameworkCoreToken : OpenIddictEntityFrameworkCoreToken { - /// - /// Represents an OpenIddict token. - /// - public class OpenIddictEntityFrameworkCoreToken : OpenIddictEntityFrameworkCoreToken + public OpenIddictEntityFrameworkCoreToken() { - public OpenIddictEntityFrameworkCoreToken() - { - // Generate a new string identifier. - Id = Guid.NewGuid().ToString(); - } + // Generate a new string identifier. + Id = Guid.NewGuid().ToString(); } +} + +/// +/// Represents an OpenIddict token. +/// +public class OpenIddictEntityFrameworkCoreToken : OpenIddictEntityFrameworkCoreToken, OpenIddictEntityFrameworkCoreAuthorization> + where TKey : notnull, IEquatable +{ +} +/// +/// Represents an OpenIddict token. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] +public class OpenIddictEntityFrameworkCoreToken + where TKey : notnull, IEquatable + where TApplication : class + where TAuthorization : class +{ /// - /// Represents an OpenIddict token. + /// Gets or sets the application associated with the current token. /// - public class OpenIddictEntityFrameworkCoreToken : OpenIddictEntityFrameworkCoreToken, OpenIddictEntityFrameworkCoreAuthorization> - where TKey : notnull, IEquatable - { - } + public virtual TApplication? Application { get; set; } /// - /// Represents an OpenIddict token. + /// Gets or sets the authorization associated with the current token. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] - public class OpenIddictEntityFrameworkCoreToken - where TKey : notnull, IEquatable - where TApplication : class - where TAuthorization : class - { - /// - /// Gets or sets the application associated with the current token. - /// - public virtual TApplication? Application { get; set; } - - /// - /// Gets or sets the authorization associated with the current token. - /// - public virtual TAuthorization? Authorization { get; set; } - - /// - /// Gets or sets the concurrency token. - /// - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - - /// - /// Gets or sets the UTC creation date of the current token. - /// - public virtual DateTime? CreationDate { get; set; } - - /// - /// Gets or sets the UTC expiration date of the current token. - /// - public virtual DateTime? ExpirationDate { get; set; } - - /// - /// Gets or sets the unique identifier associated with the current token. - /// - public virtual TKey? Id { get; set; } - - /// - /// Gets or sets the payload of the current token, if applicable. - /// Note: this property is only used for reference tokens - /// and may be encrypted for security reasons. - /// - public virtual string? Payload { get; set; } - - /// - /// Gets or sets the additional properties serialized as a JSON object, - /// or null if no bag was associated with the current token. - /// - public virtual string? Properties { get; set; } - - /// - /// Gets or sets the UTC redemption date of the current token. - /// - public virtual DateTime? RedemptionDate { get; set; } - - /// - /// Gets or sets the reference identifier associated - /// with the current token, if applicable. - /// Note: this property is only used for reference tokens - /// and may be hashed or encrypted for security reasons. - /// - public virtual string? ReferenceId { get; set; } - - /// - /// Gets or sets the status of the current token. - /// - public virtual string? Status { get; set; } - - /// - /// Gets or sets the subject associated with the current token. - /// - public virtual string? Subject { get; set; } - - /// - /// Gets or sets the type of the current token. - /// - public virtual string? Type { get; set; } - } + public virtual TAuthorization? Authorization { get; set; } + + /// + /// Gets or sets the concurrency token. + /// + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + + /// + /// Gets or sets the UTC creation date of the current token. + /// + public virtual DateTime? CreationDate { get; set; } + + /// + /// Gets or sets the UTC expiration date of the current token. + /// + public virtual DateTime? ExpirationDate { get; set; } + + /// + /// Gets or sets the unique identifier associated with the current token. + /// + public virtual TKey? Id { get; set; } + + /// + /// Gets or sets the payload of the current token, if applicable. + /// Note: this property is only used for reference tokens + /// and may be encrypted for security reasons. + /// + public virtual string? Payload { get; set; } + + /// + /// Gets or sets the additional properties serialized as a JSON object, + /// or null if no bag was associated with the current token. + /// + public virtual string? Properties { get; set; } + + /// + /// Gets or sets the UTC redemption date of the current token. + /// + public virtual DateTime? RedemptionDate { get; set; } + + /// + /// Gets or sets the reference identifier associated + /// with the current token, if applicable. + /// Note: this property is only used for reference tokens + /// and may be hashed or encrypted for security reasons. + /// + public virtual string? ReferenceId { get; set; } + + /// + /// Gets or sets the status of the current token. + /// + public virtual string? Status { get; set; } + + /// + /// Gets or sets the subject associated with the current token. + /// + public virtual string? Subject { get; set; } + + /// + /// Gets or sets the type of the current token. + /// + public virtual string? Type { get; set; } } diff --git a/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreApplicationConfiguration.cs b/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreApplicationConfiguration.cs index 04ddc60a..40693c42 100644 --- a/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreApplicationConfiguration.cs +++ b/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreApplicationConfiguration.cs @@ -10,69 +10,68 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using OpenIddict.EntityFrameworkCore.Models; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Defines a relational mapping for the Application entity. +/// +/// The type of the Application entity. +/// The type of the Authorization entity. +/// The type of the Token entity. +/// The type of the Key entity. +[EditorBrowsable(EditorBrowsableState.Never)] +public class OpenIddictEntityFrameworkCoreApplicationConfiguration : IEntityTypeConfiguration + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TToken : OpenIddictEntityFrameworkCoreToken + where TKey : notnull, IEquatable { - /// - /// Defines a relational mapping for the Application entity. - /// - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Token entity. - /// The type of the Key entity. - [EditorBrowsable(EditorBrowsableState.Never)] - public class OpenIddictEntityFrameworkCoreApplicationConfiguration : IEntityTypeConfiguration - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TToken : OpenIddictEntityFrameworkCoreToken - where TKey : notnull, IEquatable + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - // Warning: optional foreign keys MUST NOT be added as CLR properties because - // Entity Framework would throw an exception due to the TKey generic parameter - // being non-nullable when using value types like short, int, long or Guid. + // Warning: optional foreign keys MUST NOT be added as CLR properties because + // Entity Framework would throw an exception due to the TKey generic parameter + // being non-nullable when using value types like short, int, long or Guid. - builder.HasKey(application => application.Id); + builder.HasKey(application => application.Id); - // Warning: the non-generic overlord is deliberately used to work around - // a breaking change introduced in Entity Framework Core 3.x (where a - // generic entity type builder is now returned by the HasIndex() method). - builder.HasIndex(nameof(OpenIddictEntityFrameworkCoreApplication.ClientId)) - .IsUnique(); + // Warning: the non-generic overlord is deliberately used to work around + // a breaking change introduced in Entity Framework Core 3.x (where a + // generic entity type builder is now returned by the HasIndex() method). + builder.HasIndex(nameof(OpenIddictEntityFrameworkCoreApplication.ClientId)) + .IsUnique(); - builder.Property(application => application.ClientId) - .HasMaxLength(100); + builder.Property(application => application.ClientId) + .HasMaxLength(100); - builder.Property(application => application.ConcurrencyToken) - .HasMaxLength(50) - .IsConcurrencyToken(); + builder.Property(application => application.ConcurrencyToken) + .HasMaxLength(50) + .IsConcurrencyToken(); - builder.Property(application => application.ConsentType) - .HasMaxLength(50); + builder.Property(application => application.ConsentType) + .HasMaxLength(50); - builder.Property(application => application.Id) - .ValueGeneratedOnAdd(); + builder.Property(application => application.Id) + .ValueGeneratedOnAdd(); - builder.Property(application => application.Type) - .HasMaxLength(50); + builder.Property(application => application.Type) + .HasMaxLength(50); - builder.HasMany(application => application.Authorizations) - .WithOne(authorization => authorization.Application!) - .HasForeignKey(nameof(OpenIddictEntityFrameworkCoreAuthorization.Application) + - nameof(OpenIddictEntityFrameworkCoreApplication.Id)) - .IsRequired(required: false); + builder.HasMany(application => application.Authorizations) + .WithOne(authorization => authorization.Application!) + .HasForeignKey(nameof(OpenIddictEntityFrameworkCoreAuthorization.Application) + + nameof(OpenIddictEntityFrameworkCoreApplication.Id)) + .IsRequired(required: false); - builder.HasMany(application => application.Tokens) - .WithOne(token => token.Application!) - .HasForeignKey(nameof(OpenIddictEntityFrameworkCoreToken.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id)) - .IsRequired(required: false); + builder.HasMany(application => application.Tokens) + .WithOne(token => token.Application!) + .HasForeignKey(nameof(OpenIddictEntityFrameworkCoreToken.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id)) + .IsRequired(required: false); - builder.ToTable("OpenIddictApplications"); - } + builder.ToTable("OpenIddictApplications"); } } diff --git a/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreAuthorizationConfiguration.cs b/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreAuthorizationConfiguration.cs index 35b3dbfe..85c39c0e 100644 --- a/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreAuthorizationConfiguration.cs +++ b/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreAuthorizationConfiguration.cs @@ -10,64 +10,63 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using OpenIddict.EntityFrameworkCore.Models; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Defines a relational mapping for the Authorization entity. +/// +/// The type of the Authorization entity. +/// The type of the Application entity. +/// The type of the Token entity. +/// The type of the Key entity. +[EditorBrowsable(EditorBrowsableState.Never)] +public class OpenIddictEntityFrameworkCoreAuthorizationConfiguration : IEntityTypeConfiguration + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TToken : OpenIddictEntityFrameworkCoreToken + where TKey : notnull, IEquatable { - /// - /// Defines a relational mapping for the Authorization entity. - /// - /// The type of the Authorization entity. - /// The type of the Application entity. - /// The type of the Token entity. - /// The type of the Key entity. - [EditorBrowsable(EditorBrowsableState.Never)] - public class OpenIddictEntityFrameworkCoreAuthorizationConfiguration : IEntityTypeConfiguration - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TToken : OpenIddictEntityFrameworkCoreToken - where TKey : notnull, IEquatable + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - // Warning: optional foreign keys MUST NOT be added as CLR properties because - // Entity Framework would throw an exception due to the TKey generic parameter - // being non-nullable when using value types like short, int, long or Guid. + // Warning: optional foreign keys MUST NOT be added as CLR properties because + // Entity Framework would throw an exception due to the TKey generic parameter + // being non-nullable when using value types like short, int, long or Guid. - builder.HasKey(authorization => authorization.Id); + builder.HasKey(authorization => authorization.Id); - builder.HasIndex( - nameof(OpenIddictEntityFrameworkCoreAuthorization.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id), - nameof(OpenIddictEntityFrameworkCoreAuthorization.Status), - nameof(OpenIddictEntityFrameworkCoreAuthorization.Subject), - nameof(OpenIddictEntityFrameworkCoreAuthorization.Type)); + builder.HasIndex( + nameof(OpenIddictEntityFrameworkCoreAuthorization.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id), + nameof(OpenIddictEntityFrameworkCoreAuthorization.Status), + nameof(OpenIddictEntityFrameworkCoreAuthorization.Subject), + nameof(OpenIddictEntityFrameworkCoreAuthorization.Type)); - builder.Property(authorization => authorization.ConcurrencyToken) - .HasMaxLength(50) - .IsConcurrencyToken(); + builder.Property(authorization => authorization.ConcurrencyToken) + .HasMaxLength(50) + .IsConcurrencyToken(); - builder.Property(authorization => authorization.Id) - .ValueGeneratedOnAdd(); + builder.Property(authorization => authorization.Id) + .ValueGeneratedOnAdd(); - builder.Property(authorization => authorization.Status) - .HasMaxLength(50); + builder.Property(authorization => authorization.Status) + .HasMaxLength(50); - builder.Property(authorization => authorization.Subject) - .HasMaxLength(400); + builder.Property(authorization => authorization.Subject) + .HasMaxLength(400); - builder.Property(authorization => authorization.Type) - .HasMaxLength(50); + builder.Property(authorization => authorization.Type) + .HasMaxLength(50); - builder.HasMany(authorization => authorization.Tokens) - .WithOne(token => token.Authorization!) - .HasForeignKey(nameof(OpenIddictEntityFrameworkCoreToken.Authorization) + - nameof(OpenIddictEntityFrameworkCoreAuthorization.Id)) - .IsRequired(required: false); + builder.HasMany(authorization => authorization.Tokens) + .WithOne(token => token.Authorization!) + .HasForeignKey(nameof(OpenIddictEntityFrameworkCoreToken.Authorization) + + nameof(OpenIddictEntityFrameworkCoreAuthorization.Id)) + .IsRequired(required: false); - builder.ToTable("OpenIddictAuthorizations"); - } + builder.ToTable("OpenIddictAuthorizations"); } } diff --git a/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreScopeConfiguration.cs b/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreScopeConfiguration.cs index 8f16dd25..c7cd14a0 100644 --- a/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreScopeConfiguration.cs +++ b/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreScopeConfiguration.cs @@ -10,48 +10,47 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using OpenIddict.EntityFrameworkCore.Models; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Defines a relational mapping for the Scope entity. +/// +/// The type of the Scope entity. +/// The type of the Key entity. +[EditorBrowsable(EditorBrowsableState.Never)] +public class OpenIddictEntityFrameworkCoreScopeConfiguration : IEntityTypeConfiguration + where TScope : OpenIddictEntityFrameworkCoreScope + where TKey : notnull, IEquatable { - /// - /// Defines a relational mapping for the Scope entity. - /// - /// The type of the Scope entity. - /// The type of the Key entity. - [EditorBrowsable(EditorBrowsableState.Never)] - public class OpenIddictEntityFrameworkCoreScopeConfiguration : IEntityTypeConfiguration - where TScope : OpenIddictEntityFrameworkCoreScope - where TKey : notnull, IEquatable + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - // Warning: optional foreign keys MUST NOT be added as CLR properties because - // Entity Framework would throw an exception due to the TKey generic parameter - // being non-nullable when using value types like short, int, long or Guid. + // Warning: optional foreign keys MUST NOT be added as CLR properties because + // Entity Framework would throw an exception due to the TKey generic parameter + // being non-nullable when using value types like short, int, long or Guid. - builder.HasKey(scope => scope.Id); + builder.HasKey(scope => scope.Id); - // Warning: the non-generic overlord is deliberately used to work around - // a breaking change introduced in Entity Framework Core 3.x (where a - // generic entity type builder is now returned by the HasIndex() method). - builder.HasIndex(nameof(OpenIddictEntityFrameworkCoreScope.Name)) - .IsUnique(); + // Warning: the non-generic overlord is deliberately used to work around + // a breaking change introduced in Entity Framework Core 3.x (where a + // generic entity type builder is now returned by the HasIndex() method). + builder.HasIndex(nameof(OpenIddictEntityFrameworkCoreScope.Name)) + .IsUnique(); - builder.Property(scope => scope.ConcurrencyToken) - .HasMaxLength(50) - .IsConcurrencyToken(); + builder.Property(scope => scope.ConcurrencyToken) + .HasMaxLength(50) + .IsConcurrencyToken(); - builder.Property(scope => scope.Id) - .ValueGeneratedOnAdd(); + builder.Property(scope => scope.Id) + .ValueGeneratedOnAdd(); - builder.Property(scope => scope.Name) - .HasMaxLength(200); + builder.Property(scope => scope.Name) + .HasMaxLength(200); - builder.ToTable("OpenIddictScopes"); - } + builder.ToTable("OpenIddictScopes"); } } diff --git a/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreTokenConfiguration.cs b/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreTokenConfiguration.cs index d9b85ab2..984e7a5e 100644 --- a/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreTokenConfiguration.cs +++ b/src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreTokenConfiguration.cs @@ -10,67 +10,66 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using OpenIddict.EntityFrameworkCore.Models; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Defines a relational mapping for the Token entity. +/// +/// The type of the Token entity. +/// The type of the Application entity. +/// The type of the Authorization entity. +/// The type of the Key entity. +[EditorBrowsable(EditorBrowsableState.Never)] +public class OpenIddictEntityFrameworkCoreTokenConfiguration : IEntityTypeConfiguration + where TToken : OpenIddictEntityFrameworkCoreToken + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TKey : notnull, IEquatable { - /// - /// Defines a relational mapping for the Token entity. - /// - /// The type of the Token entity. - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Key entity. - [EditorBrowsable(EditorBrowsableState.Never)] - public class OpenIddictEntityFrameworkCoreTokenConfiguration : IEntityTypeConfiguration - where TToken : OpenIddictEntityFrameworkCoreToken - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TKey : notnull, IEquatable + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - // Warning: optional foreign keys MUST NOT be added as CLR properties because - // Entity Framework would throw an exception due to the TKey generic parameter - // being non-nullable when using value types like short, int, long or Guid. + // Warning: optional foreign keys MUST NOT be added as CLR properties because + // Entity Framework would throw an exception due to the TKey generic parameter + // being non-nullable when using value types like short, int, long or Guid. - builder.HasKey(token => token.Id); + builder.HasKey(token => token.Id); - // Warning: the non-generic overlord is deliberately used to work around - // a breaking change introduced in Entity Framework Core 3.x (where a - // generic entity type builder is now returned by the HasIndex() method). - builder.HasIndex(nameof(OpenIddictEntityFrameworkCoreToken.ReferenceId)) - .IsUnique(); + // Warning: the non-generic overlord is deliberately used to work around + // a breaking change introduced in Entity Framework Core 3.x (where a + // generic entity type builder is now returned by the HasIndex() method). + builder.HasIndex(nameof(OpenIddictEntityFrameworkCoreToken.ReferenceId)) + .IsUnique(); - builder.HasIndex( - nameof(OpenIddictEntityFrameworkCoreToken.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id), - nameof(OpenIddictEntityFrameworkCoreToken.Status), - nameof(OpenIddictEntityFrameworkCoreToken.Subject), - nameof(OpenIddictEntityFrameworkCoreToken.Type)); + builder.HasIndex( + nameof(OpenIddictEntityFrameworkCoreToken.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id), + nameof(OpenIddictEntityFrameworkCoreToken.Status), + nameof(OpenIddictEntityFrameworkCoreToken.Subject), + nameof(OpenIddictEntityFrameworkCoreToken.Type)); - builder.Property(token => token.ConcurrencyToken) - .HasMaxLength(50) - .IsConcurrencyToken(); + builder.Property(token => token.ConcurrencyToken) + .HasMaxLength(50) + .IsConcurrencyToken(); - builder.Property(token => token.Id) - .ValueGeneratedOnAdd(); + builder.Property(token => token.Id) + .ValueGeneratedOnAdd(); - builder.Property(token => token.ReferenceId) - .HasMaxLength(100); + builder.Property(token => token.ReferenceId) + .HasMaxLength(100); - builder.Property(token => token.Status) - .HasMaxLength(50); + builder.Property(token => token.Status) + .HasMaxLength(50); - builder.Property(token => token.Subject) - .HasMaxLength(400); + builder.Property(token => token.Subject) + .HasMaxLength(400); - builder.Property(token => token.Type) - .HasMaxLength(50); + builder.Property(token => token.Type) + .HasMaxLength(50); - builder.ToTable("OpenIddictTokens"); - } + builder.ToTable("OpenIddictTokens"); } } diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs index 32a8fb8f..ce9240d6 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs @@ -12,118 +12,117 @@ using OpenIddict.EntityFrameworkCore; using OpenIddict.EntityFrameworkCore.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the OpenIddict Entity Framework Core services. +/// +public class OpenIddictEntityFrameworkCoreBuilder { /// - /// Exposes the necessary methods required to configure the OpenIddict Entity Framework Core services. + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictEntityFrameworkCoreBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. /// - public class OpenIddictEntityFrameworkCoreBuilder + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict Entity Framework Core configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictEntityFrameworkCoreBuilder Configure(Action configuration) { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictEntityFrameworkCoreBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Amends the default OpenIddict Entity Framework Core configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictEntityFrameworkCoreBuilder Configure(Action configuration) + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Configures OpenIddict to use the default OpenIddict - /// Entity Framework Core entities, with the specified key type. - /// - /// The . - public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities() - where TKey : notnull, IEquatable - => ReplaceDefaultEntities, - OpenIddictEntityFrameworkCoreAuthorization, - OpenIddictEntityFrameworkCoreScope, - OpenIddictEntityFrameworkCoreToken, TKey>(); - - /// - /// Configures OpenIddict to use the specified entities, derived - /// from the default OpenIddict Entity Framework Core entities. - /// - /// The . - public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities() - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TScope : OpenIddictEntityFrameworkCoreScope - where TToken : OpenIddictEntityFrameworkCoreToken - where TKey : notnull, IEquatable - { - Services.Configure(options => - { - options.DefaultApplicationType = typeof(TApplication); - options.DefaultAuthorizationType = typeof(TAuthorization); - options.DefaultScopeType = typeof(TScope); - options.DefaultTokenType = typeof(TToken); - }); - - return this; - } + /// + /// Configures OpenIddict to use the default OpenIddict + /// Entity Framework Core entities, with the specified key type. + /// + /// The . + public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities() + where TKey : notnull, IEquatable + => ReplaceDefaultEntities, + OpenIddictEntityFrameworkCoreAuthorization, + OpenIddictEntityFrameworkCoreScope, + OpenIddictEntityFrameworkCoreToken, TKey>(); - /// - /// Configures the OpenIddict Entity Framework Core stores to use the specified database context type. - /// - /// The type of the used by OpenIddict. - /// The . - public OpenIddictEntityFrameworkCoreBuilder UseDbContext() - where TContext : DbContext - => UseDbContext(typeof(TContext)); - - /// - /// Configures the OpenIddict Entity Framework Core stores to use the specified database context type. - /// - /// The type of the used by OpenIddict. - /// The . - public OpenIddictEntityFrameworkCoreBuilder UseDbContext(Type type) + /// + /// Configures OpenIddict to use the specified entities, derived + /// from the default OpenIddict Entity Framework Core entities. + /// + /// The . + public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities() + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TScope : OpenIddictEntityFrameworkCoreScope + where TToken : OpenIddictEntityFrameworkCoreToken + where TKey : notnull, IEquatable + { + Services.Configure(options => { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + options.DefaultApplicationType = typeof(TApplication); + options.DefaultAuthorizationType = typeof(TAuthorization); + options.DefaultScopeType = typeof(TScope); + options.DefaultTokenType = typeof(TToken); + }); - if (!typeof(DbContext).IsAssignableFrom(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); - } + return this; + } - return Configure(options => options.DbContextType = type); - } + /// + /// Configures the OpenIddict Entity Framework Core stores to use the specified database context type. + /// + /// The type of the used by OpenIddict. + /// The . + public OpenIddictEntityFrameworkCoreBuilder UseDbContext() + where TContext : DbContext + => UseDbContext(typeof(TContext)); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + /// + /// Configures the OpenIddict Entity Framework Core stores to use the specified database context type. + /// + /// The type of the used by OpenIddict. + /// The . + public OpenIddictEntityFrameworkCoreBuilder UseDbContext(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + if (!typeof(DbContext).IsAssignableFrom(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type)); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); + return Configure(options => options.DbContextType = type); } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs index 0994cb42..156ed573 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs @@ -9,40 +9,39 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using OpenIddict.EntityFrameworkCore.Models; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Represents a model customizer able to register the entity sets +/// required by the OpenIddict stack in an Entity Framework Core context. +/// +public class OpenIddictEntityFrameworkCoreCustomizer : RelationalModelCustomizer + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TScope : OpenIddictEntityFrameworkCoreScope + where TToken : OpenIddictEntityFrameworkCoreToken + where TKey : notnull, IEquatable { - /// - /// Represents a model customizer able to register the entity sets - /// required by the OpenIddict stack in an Entity Framework Core context. - /// - public class OpenIddictEntityFrameworkCoreCustomizer : RelationalModelCustomizer - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TScope : OpenIddictEntityFrameworkCoreScope - where TToken : OpenIddictEntityFrameworkCoreToken - where TKey : notnull, IEquatable + public OpenIddictEntityFrameworkCoreCustomizer(ModelCustomizerDependencies dependencies) + : base(dependencies) + { + } + + public override void Customize(ModelBuilder modelBuilder, DbContext context) { - public OpenIddictEntityFrameworkCoreCustomizer(ModelCustomizerDependencies dependencies) - : base(dependencies) + if (modelBuilder is null) { + throw new ArgumentNullException(nameof(modelBuilder)); } - public override void Customize(ModelBuilder modelBuilder, DbContext context) + if (context is null) { - if (modelBuilder is null) - { - throw new ArgumentNullException(nameof(modelBuilder)); - } - - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // Register the OpenIddict entity sets. - modelBuilder.UseOpenIddict(); + // Register the OpenIddict entity sets. + modelBuilder.UseOpenIddict(); - base.Customize(modelBuilder, context); - } + base.Customize(modelBuilder, context); } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs index 397b61ba..4f24cf5a 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs @@ -9,79 +9,78 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using OpenIddict.EntityFrameworkCore; using OpenIddict.EntityFrameworkCore.Models; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict Entity Framework Core services. +/// +public static class OpenIddictEntityFrameworkCoreExtensions { /// - /// Exposes extensions allowing to register the OpenIddict Entity Framework Core services. + /// Registers the Entity Framework Core stores services in the DI container and + /// configures OpenIddict to use the Entity Framework Core entities by default. /// - public static class OpenIddictEntityFrameworkCoreExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictEntityFrameworkCoreBuilder UseEntityFrameworkCore(this OpenIddictCoreBuilder builder) { - /// - /// Registers the Entity Framework Core stores services in the DI container and - /// configures OpenIddict to use the Entity Framework Core entities by default. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictEntityFrameworkCoreBuilder UseEntityFrameworkCore(this OpenIddictCoreBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - // Since Entity Framework Core may be used with databases performing case-insensitive - // or culture-sensitive comparisons, ensure the additional filtering logic is enforced - // in case case-sensitive stores were registered before this extension was called. - builder.Configure(options => options.DisableAdditionalFiltering = false); + // Since Entity Framework Core may be used with databases performing case-insensitive + // or culture-sensitive comparisons, ensure the additional filtering logic is enforced + // in case case-sensitive stores were registered before this extension was called. + builder.Configure(options => options.DisableAdditionalFiltering = false); - builder.SetDefaultApplicationEntity() - .SetDefaultAuthorizationEntity() - .SetDefaultScopeEntity() - .SetDefaultTokenEntity(); + builder.SetDefaultApplicationEntity() + .SetDefaultAuthorizationEntity() + .SetDefaultScopeEntity() + .SetDefaultTokenEntity(); - builder.ReplaceApplicationStoreResolver() - .ReplaceAuthorizationStoreResolver() - .ReplaceScopeStoreResolver() - .ReplaceTokenStoreResolver(); + builder.ReplaceApplicationStoreResolver() + .ReplaceAuthorizationStoreResolver() + .ReplaceScopeStoreResolver() + .ReplaceTokenStoreResolver(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); - builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreApplicationStore<,,,,>)); - builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<,,,,>)); - builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreScopeStore<,,>)); - builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreTokenStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreApplicationStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<,,,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreScopeStore<,,>)); + builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreTokenStore<,,,,>)); - return new OpenIddictEntityFrameworkCoreBuilder(builder.Services); - } + return new OpenIddictEntityFrameworkCoreBuilder(builder.Services); + } - /// - /// Registers the Entity Framework Core stores services in the DI container and - /// configures OpenIddict to use the Entity Framework Core entities by default. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the Entity Framework Core services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictCoreBuilder UseEntityFrameworkCore( - this OpenIddictCoreBuilder builder, Action configuration) + /// + /// Registers the Entity Framework Core stores services in the DI container and + /// configures OpenIddict to use the Entity Framework Core entities by default. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the Entity Framework Core services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictCoreBuilder UseEntityFrameworkCore( + this OpenIddictCoreBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseEntityFrameworkCore()); + configuration(builder.UseEntityFrameworkCore()); - return builder; - } + return builder; } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs index 7926201d..f79527db 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs @@ -15,166 +15,165 @@ using Microsoft.Extensions.DependencyInjection; using OpenIddict.EntityFrameworkCore; using OpenIddict.EntityFrameworkCore.Models; -namespace Microsoft.EntityFrameworkCore +namespace Microsoft.EntityFrameworkCore; + +/// +/// Exposes extensions simplifying the integration between OpenIddict and Entity Framework Core. +/// +public static class OpenIddictEntityFrameworkCoreHelpers { /// - /// Exposes extensions simplifying the integration between OpenIddict and Entity Framework Core. + /// Registers the OpenIddict entity sets in the Entity Framework Core context + /// using the default OpenIddict models and the default key type (string). /// - public static class OpenIddictEntityFrameworkCoreHelpers - { - /// - /// Registers the OpenIddict entity sets in the Entity Framework Core context - /// using the default OpenIddict models and the default key type (string). - /// - /// The builder used to configure the Entity Framework context. - /// The Entity Framework context builder. - public static DbContextOptionsBuilder UseOpenIddict(this DbContextOptionsBuilder builder) - => builder.UseOpenIddict(); + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static DbContextOptionsBuilder UseOpenIddict(this DbContextOptionsBuilder builder) + => builder.UseOpenIddict(); - /// - /// Registers the OpenIddict entity sets in the Entity Framework Core - /// context using the default OpenIddict models and the specified key type. - /// - /// - /// Note: when using a custom key type, the new key type MUST be registered by calling - /// . - /// - /// The builder used to configure the Entity Framework context. - /// The Entity Framework context builder. - public static DbContextOptionsBuilder UseOpenIddict(this DbContextOptionsBuilder builder) - where TKey : notnull, IEquatable - => builder.UseOpenIddict, - OpenIddictEntityFrameworkCoreAuthorization, - OpenIddictEntityFrameworkCoreScope, - OpenIddictEntityFrameworkCoreToken, TKey>(); + /// + /// Registers the OpenIddict entity sets in the Entity Framework Core + /// context using the default OpenIddict models and the specified key type. + /// + /// + /// Note: when using a custom key type, the new key type MUST be registered by calling + /// . + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static DbContextOptionsBuilder UseOpenIddict(this DbContextOptionsBuilder builder) + where TKey : notnull, IEquatable + => builder.UseOpenIddict, + OpenIddictEntityFrameworkCoreAuthorization, + OpenIddictEntityFrameworkCoreScope, + OpenIddictEntityFrameworkCoreToken, TKey>(); - /// - /// Registers the OpenIddict entity sets in the Entity Framework Core - /// context using the specified entities and the specified key type. - /// - /// - /// Note: when using custom entities, the new entities MUST be registered by calling - /// . - /// - /// The builder used to configure the Entity Framework context. - /// The Entity Framework context builder. - public static DbContextOptionsBuilder UseOpenIddict( - this DbContextOptionsBuilder builder) - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TScope : OpenIddictEntityFrameworkCoreScope - where TToken : OpenIddictEntityFrameworkCoreToken - where TKey : notnull, IEquatable + /// + /// Registers the OpenIddict entity sets in the Entity Framework Core + /// context using the specified entities and the specified key type. + /// + /// + /// Note: when using custom entities, the new entities MUST be registered by calling + /// . + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static DbContextOptionsBuilder UseOpenIddict( + this DbContextOptionsBuilder builder) + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TScope : OpenIddictEntityFrameworkCoreScope + where TToken : OpenIddictEntityFrameworkCoreToken + where TKey : notnull, IEquatable + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.ReplaceService>(); + throw new ArgumentNullException(nameof(builder)); } - /// - /// Registers the OpenIddict entity sets in the Entity Framework Core context - /// using the default OpenIddict models and the default key type (string). - /// - /// The builder used to configure the Entity Framework context. - /// The Entity Framework context builder. - public static ModelBuilder UseOpenIddict(this ModelBuilder builder) - => builder.UseOpenIddict(); + return builder.ReplaceService>(); + } - /// - /// Registers the OpenIddict entity sets in the Entity Framework Core - /// context using the default OpenIddict models and the specified key type. - /// - /// - /// Note: when using a custom key type, the new key type MUST be registered by calling - /// . - /// - /// The builder used to configure the Entity Framework context. - /// The Entity Framework context builder. - public static ModelBuilder UseOpenIddict(this ModelBuilder builder) where TKey : notnull, IEquatable - => builder.UseOpenIddict, - OpenIddictEntityFrameworkCoreAuthorization, - OpenIddictEntityFrameworkCoreScope, - OpenIddictEntityFrameworkCoreToken, TKey>(); + /// + /// Registers the OpenIddict entity sets in the Entity Framework Core context + /// using the default OpenIddict models and the default key type (string). + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static ModelBuilder UseOpenIddict(this ModelBuilder builder) + => builder.UseOpenIddict(); - /// - /// Registers the OpenIddict entity sets in the Entity Framework Core - /// context using the specified entities and the specified key type. - /// - /// - /// Note: when using custom entities, the new entities MUST be registered by calling - /// . - /// - /// The builder used to configure the Entity Framework context. - /// The Entity Framework context builder. - public static ModelBuilder UseOpenIddict(this ModelBuilder builder) - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TScope : OpenIddictEntityFrameworkCoreScope - where TToken : OpenIddictEntityFrameworkCoreToken - where TKey : notnull, IEquatable - { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + /// + /// Registers the OpenIddict entity sets in the Entity Framework Core + /// context using the default OpenIddict models and the specified key type. + /// + /// + /// Note: when using a custom key type, the new key type MUST be registered by calling + /// . + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static ModelBuilder UseOpenIddict(this ModelBuilder builder) where TKey : notnull, IEquatable + => builder.UseOpenIddict, + OpenIddictEntityFrameworkCoreAuthorization, + OpenIddictEntityFrameworkCoreScope, + OpenIddictEntityFrameworkCoreToken, TKey>(); - return builder - .ApplyConfiguration(new OpenIddictEntityFrameworkCoreApplicationConfiguration()) - .ApplyConfiguration(new OpenIddictEntityFrameworkCoreAuthorizationConfiguration()) - .ApplyConfiguration(new OpenIddictEntityFrameworkCoreScopeConfiguration()) - .ApplyConfiguration(new OpenIddictEntityFrameworkCoreTokenConfiguration()); + /// + /// Registers the OpenIddict entity sets in the Entity Framework Core + /// context using the specified entities and the specified key type. + /// + /// + /// Note: when using custom entities, the new entities MUST be registered by calling + /// . + /// + /// The builder used to configure the Entity Framework context. + /// The Entity Framework context builder. + public static ModelBuilder UseOpenIddict(this ModelBuilder builder) + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TScope : OpenIddictEntityFrameworkCoreScope + where TToken : OpenIddictEntityFrameworkCoreToken + where TKey : notnull, IEquatable + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); } + return builder + .ApplyConfiguration(new OpenIddictEntityFrameworkCoreApplicationConfiguration()) + .ApplyConfiguration(new OpenIddictEntityFrameworkCoreAuthorizationConfiguration()) + .ApplyConfiguration(new OpenIddictEntityFrameworkCoreScopeConfiguration()) + .ApplyConfiguration(new OpenIddictEntityFrameworkCoreTokenConfiguration()); + } + #if SUPPORTS_BCL_ASYNC_ENUMERABLE - /// - /// Executes the query and returns the results as a streamed async enumeration. - /// - /// The type of the returned entities. - /// The query source. - /// The that can be used to abort the operation. - /// The non-streamed async enumeration containing the results. + /// + /// Executes the query and returns the results as a streamed async enumeration. + /// + /// The type of the returned entities. + /// The query source. + /// The that can be used to abort the operation. + /// The non-streamed async enumeration containing the results. #else - /// - /// Executes the query and returns the results as a non-streamed async enumeration. - /// - /// The type of the returned entities. - /// The query source. - /// The that can be used to abort the operation. - /// The non-streamed async enumeration containing the results. + /// + /// Executes the query and returns the results as a non-streamed async enumeration. + /// + /// The type of the returned entities. + /// The query source. + /// The that can be used to abort the operation. + /// The non-streamed async enumeration containing the results. #endif - internal static IAsyncEnumerable AsAsyncEnumerable(this IQueryable source, CancellationToken cancellationToken) + internal static IAsyncEnumerable AsAsyncEnumerable(this IQueryable source, CancellationToken cancellationToken) + { + if (source is null) { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } + throw new ArgumentNullException(nameof(source)); + } - return ExecuteAsync(source, cancellationToken); + return ExecuteAsync(source, cancellationToken); - static async IAsyncEnumerable ExecuteAsync(IQueryable source, [EnumeratorCancellation] CancellationToken cancellationToken) - { + static async IAsyncEnumerable ExecuteAsync(IQueryable source, [EnumeratorCancellation] CancellationToken cancellationToken) + { #if SUPPORTS_BCL_ASYNC_ENUMERABLE - await foreach (var element in source.AsAsyncEnumerable().WithCancellation(cancellationToken)) - { - yield return element; - } + await foreach (var element in source.AsAsyncEnumerable().WithCancellation(cancellationToken)) + { + yield return element; + } #else - foreach (var element in await source.ToListAsync(cancellationToken)) - { - yield return element; - } -#endif + foreach (var element in await source.ToListAsync(cancellationToken)) + { + yield return element; } +#endif } } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs index c786dc3e..e06d8197 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs @@ -7,19 +7,18 @@ using System; using Microsoft.EntityFrameworkCore; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Provides various settings needed to configure +/// the OpenIddict Entity Framework Core integration. +/// +public class OpenIddictEntityFrameworkCoreOptions { /// - /// Provides various settings needed to configure - /// the OpenIddict Entity Framework Core integration. + /// Gets or sets the concrete type of the used by the + /// OpenIddict Entity Framework Core stores. If this property is not populated, + /// an exception is thrown at runtime when trying to use the stores. /// - public class OpenIddictEntityFrameworkCoreOptions - { - /// - /// Gets or sets the concrete type of the used by the - /// OpenIddict Entity Framework Core stores. If this property is not populated, - /// an exception is thrown at runtime when trying to use the stores. - /// - public Type? DbContextType { get; set; } - } + public Type? DbContextType { get; set; } } diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolver.cs index 4d85e87b..188e30d9 100644 --- a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolver.cs +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolver.cs @@ -13,70 +13,69 @@ using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.Extensions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Exposes a method allowing to resolve an application store. +/// +public class OpenIddictEntityFrameworkCoreApplicationStoreResolver : IOpenIddictApplicationStoreResolver { + private readonly TypeResolutionCache _cache; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + public OpenIddictEntityFrameworkCoreApplicationStoreResolver( + TypeResolutionCache cache, + IOptionsMonitor options, + IServiceProvider provider) + { + _cache = cache; + _options = options; + _provider = provider; + } + /// - /// Exposes a method allowing to resolve an application store. + /// Returns an application store compatible with the specified application type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictEntityFrameworkCoreApplicationStoreResolver : IOpenIddictApplicationStoreResolver + /// The type of the Application entity. + /// An . + public IOpenIddictApplicationStore Get() where TApplication : class { - private readonly TypeResolutionCache _cache; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - public OpenIddictEntityFrameworkCoreApplicationStoreResolver( - TypeResolutionCache cache, - IOptionsMonitor options, - IServiceProvider provider) + var store = _provider.GetService>(); + if (store is not null) { - _cache = cache; - _options = options; - _provider = provider; + return store; } - /// - /// Returns an application store compatible with the specified application type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Application entity. - /// An . - public IOpenIddictApplicationStore Get() where TApplication : class + var type = _cache.GetOrAdd(typeof(TApplication), key => { - var store = _provider.GetService>(); - if (store is not null) + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreApplication<,,>)); + if (root is null) { - return store; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0252)); } - var type = _cache.GetOrAdd(typeof(TApplication), key => + var context = _options.CurrentValue.DbContextType; + if (context is null) { - var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreApplication<,,>)); - if (root is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0252)); - } - - var context = _options.CurrentValue.DbContextType; - if (context is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0253)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0253)); + } - return typeof(OpenIddictEntityFrameworkCoreApplicationStore<,,,,>).MakeGenericType( - /* TApplication: */ key, - /* TAuthorization: */ root.GenericTypeArguments[1], - /* TToken: */ root.GenericTypeArguments[2], - /* TContext: */ context, - /* TKey: */ root.GenericTypeArguments[0]); - }); + return typeof(OpenIddictEntityFrameworkCoreApplicationStore<,,,,>).MakeGenericType( + /* TApplication: */ key, + /* TAuthorization: */ root.GenericTypeArguments[1], + /* TToken: */ root.GenericTypeArguments[2], + /* TContext: */ context, + /* TKey: */ root.GenericTypeArguments[0]); + }); - return (IOpenIddictApplicationStore) _provider.GetRequiredService(type); - } - - // Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner - // service provider must be able to resolve scoped services (typically, the store they return). - // To avoid having to declare a static type resolution cache, a special cache service is used - // here and registered as a singleton dependency so that its content persists beyond the scope. - public class TypeResolutionCache : ConcurrentDictionary { } + return (IOpenIddictApplicationStore) _provider.GetRequiredService(type); } + + // Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner + // service provider must be able to resolve scoped services (typically, the store they return). + // To avoid having to declare a static type resolution cache, a special cache service is used + // here and registered as a singleton dependency so that its content persists beyond the scope. + public class TypeResolutionCache : ConcurrentDictionary { } } diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.cs index 6b473389..9d654bdf 100644 --- a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.cs +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.cs @@ -13,70 +13,69 @@ using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.Extensions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Exposes a method allowing to resolve an authorization store. +/// +public class OpenIddictEntityFrameworkCoreAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver { + private readonly TypeResolutionCache _cache; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + public OpenIddictEntityFrameworkCoreAuthorizationStoreResolver( + TypeResolutionCache cache, + IOptionsMonitor options, + IServiceProvider provider) + { + _cache = cache; + _options = options; + _provider = provider; + } + /// - /// Exposes a method allowing to resolve an authorization store. + /// Returns an authorization store compatible with the specified authorization type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictEntityFrameworkCoreAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver + /// The type of the Authorization entity. + /// An . + public IOpenIddictAuthorizationStore Get() where TAuthorization : class { - private readonly TypeResolutionCache _cache; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - public OpenIddictEntityFrameworkCoreAuthorizationStoreResolver( - TypeResolutionCache cache, - IOptionsMonitor options, - IServiceProvider provider) + var store = _provider.GetService>(); + if (store is not null) { - _cache = cache; - _options = options; - _provider = provider; + return store; } - /// - /// Returns an authorization store compatible with the specified authorization type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Authorization entity. - /// An . - public IOpenIddictAuthorizationStore Get() where TAuthorization : class + var type = _cache.GetOrAdd(typeof(TAuthorization), key => { - var store = _provider.GetService>(); - if (store is not null) + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreAuthorization<,,>)); + if (root is null) { - return store; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0254)); } - var type = _cache.GetOrAdd(typeof(TAuthorization), key => + var context = _options.CurrentValue.DbContextType; + if (context is null) { - var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreAuthorization<,,>)); - if (root is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0254)); - } - - var context = _options.CurrentValue.DbContextType; - if (context is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0253)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0253)); + } - return typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<,,,,>).MakeGenericType( - /* TAuthorization: */ key, - /* TApplication: */ root.GenericTypeArguments[1], - /* TToken: */ root.GenericTypeArguments[2], - /* TContext: */ context, - /* TKey: */ root.GenericTypeArguments[0]); - }); + return typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<,,,,>).MakeGenericType( + /* TAuthorization: */ key, + /* TApplication: */ root.GenericTypeArguments[1], + /* TToken: */ root.GenericTypeArguments[2], + /* TContext: */ context, + /* TKey: */ root.GenericTypeArguments[0]); + }); - return (IOpenIddictAuthorizationStore) _provider.GetRequiredService(type); - } - - // Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner - // service provider must be able to resolve scoped services (typically, the store they return). - // To avoid having to declare a static type resolution cache, a special cache service is used - // here and registered as a singleton dependency so that its content persists beyond the scope. - public class TypeResolutionCache : ConcurrentDictionary { } + return (IOpenIddictAuthorizationStore) _provider.GetRequiredService(type); } + + // Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner + // service provider must be able to resolve scoped services (typically, the store they return). + // To avoid having to declare a static type resolution cache, a special cache service is used + // here and registered as a singleton dependency so that its content persists beyond the scope. + public class TypeResolutionCache : ConcurrentDictionary { } } diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolver.cs index ba27f58a..934b1a11 100644 --- a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolver.cs +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolver.cs @@ -13,68 +13,67 @@ using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.Extensions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Exposes a method allowing to resolve a scope store. +/// +public class OpenIddictEntityFrameworkCoreScopeStoreResolver : IOpenIddictScopeStoreResolver { + private readonly TypeResolutionCache _cache; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + public OpenIddictEntityFrameworkCoreScopeStoreResolver( + TypeResolutionCache cache, + IOptionsMonitor options, + IServiceProvider provider) + { + _cache = cache; + _options = options; + _provider = provider; + } + /// - /// Exposes a method allowing to resolve a scope store. + /// Returns a scope store compatible with the specified scope type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictEntityFrameworkCoreScopeStoreResolver : IOpenIddictScopeStoreResolver + /// The type of the Scope entity. + /// An . + public IOpenIddictScopeStore Get() where TScope : class { - private readonly TypeResolutionCache _cache; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - public OpenIddictEntityFrameworkCoreScopeStoreResolver( - TypeResolutionCache cache, - IOptionsMonitor options, - IServiceProvider provider) + var store = _provider.GetService>(); + if (store is not null) { - _cache = cache; - _options = options; - _provider = provider; + return store; } - /// - /// Returns a scope store compatible with the specified scope type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Scope entity. - /// An . - public IOpenIddictScopeStore Get() where TScope : class + var type = _cache.GetOrAdd(typeof(TScope), key => { - var store = _provider.GetService>(); - if (store is not null) + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreScope<>)); + if (root is null) { - return store; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0255)); } - var type = _cache.GetOrAdd(typeof(TScope), key => + var context = _options.CurrentValue.DbContextType; + if (context is null) { - var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreScope<>)); - if (root is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0255)); - } - - var context = _options.CurrentValue.DbContextType; - if (context is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0253)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0253)); + } - return typeof(OpenIddictEntityFrameworkCoreScopeStore<,,>).MakeGenericType( - /* TScope: */ key, - /* TContext: */ context, - /* TKey: */ root.GenericTypeArguments[0]); - }); + return typeof(OpenIddictEntityFrameworkCoreScopeStore<,,>).MakeGenericType( + /* TScope: */ key, + /* TContext: */ context, + /* TKey: */ root.GenericTypeArguments[0]); + }); - return (IOpenIddictScopeStore) _provider.GetRequiredService(type); - } - - // Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner - // service provider must be able to resolve scoped services (typically, the store they return). - // To avoid having to declare a static type resolution cache, a special cache service is used - // here and registered as a singleton dependency so that its content persists beyond the scope. - public class TypeResolutionCache : ConcurrentDictionary { } + return (IOpenIddictScopeStore) _provider.GetRequiredService(type); } + + // Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner + // service provider must be able to resolve scoped services (typically, the store they return). + // To avoid having to declare a static type resolution cache, a special cache service is used + // here and registered as a singleton dependency so that its content persists beyond the scope. + public class TypeResolutionCache : ConcurrentDictionary { } } diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolver.cs index 98638401..9a970bcf 100644 --- a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolver.cs +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolver.cs @@ -13,70 +13,69 @@ using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.Extensions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Exposes a method allowing to resolve a token store. +/// +public class OpenIddictEntityFrameworkCoreTokenStoreResolver : IOpenIddictTokenStoreResolver { + private readonly TypeResolutionCache _cache; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + public OpenIddictEntityFrameworkCoreTokenStoreResolver( + TypeResolutionCache cache, + IOptionsMonitor options, + IServiceProvider provider) + { + _cache = cache; + _options = options; + _provider = provider; + } + /// - /// Exposes a method allowing to resolve a token store. + /// Returns a token store compatible with the specified token type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictEntityFrameworkCoreTokenStoreResolver : IOpenIddictTokenStoreResolver + /// The type of the Token entity. + /// An . + public IOpenIddictTokenStore Get() where TToken : class { - private readonly TypeResolutionCache _cache; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - public OpenIddictEntityFrameworkCoreTokenStoreResolver( - TypeResolutionCache cache, - IOptionsMonitor options, - IServiceProvider provider) + var store = _provider.GetService>(); + if (store is not null) { - _cache = cache; - _options = options; - _provider = provider; + return store; } - /// - /// Returns a token store compatible with the specified token type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Token entity. - /// An . - public IOpenIddictTokenStore Get() where TToken : class + var type = _cache.GetOrAdd(typeof(TToken), key => { - var store = _provider.GetService>(); - if (store is not null) + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreToken<,,>)); + if (root is null) { - return store; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0256)); } - var type = _cache.GetOrAdd(typeof(TToken), key => + var context = _options.CurrentValue.DbContextType; + if (context is null) { - var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreToken<,,>)); - if (root is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0256)); - } - - var context = _options.CurrentValue.DbContextType; - if (context is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0253)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0253)); + } - return typeof(OpenIddictEntityFrameworkCoreTokenStore<,,,,>).MakeGenericType( - /* TToken: */ key, - /* TApplication: */ root.GenericTypeArguments[1], - /* TAuthorization: */ root.GenericTypeArguments[2], - /* TContext: */ context, - /* TKey: */ root.GenericTypeArguments[0]); - }); + return typeof(OpenIddictEntityFrameworkCoreTokenStore<,,,,>).MakeGenericType( + /* TToken: */ key, + /* TApplication: */ root.GenericTypeArguments[1], + /* TAuthorization: */ root.GenericTypeArguments[2], + /* TContext: */ context, + /* TKey: */ root.GenericTypeArguments[0]); + }); - return (IOpenIddictTokenStore) _provider.GetRequiredService(type); - } - - // Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner - // service provider must be able to resolve scoped services (typically, the store they return). - // To avoid having to declare a static type resolution cache, a special cache service is used - // here and registered as a singleton dependency so that its content persists beyond the scope. - public class TypeResolutionCache : ConcurrentDictionary { } + return (IOpenIddictTokenStore) _provider.GetRequiredService(type); } + + // Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner + // service provider must be able to resolve scoped services (typically, the store they return). + // To avoid having to declare a static type resolution cache, a special cache service is used + // here and registered as a singleton dependency so that its content persists beyond the scope. + public class TypeResolutionCache : ConcurrentDictionary { } } diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs index 9a5a8b10..c29b8950 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs @@ -27,1049 +27,1048 @@ using OpenIddict.Abstractions; using OpenIddict.EntityFrameworkCore.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Provides methods allowing to manage the applications stored in a database. +/// +/// The type of the Entity Framework database context. +public class OpenIddictEntityFrameworkCoreApplicationStore : + OpenIddictEntityFrameworkCoreApplicationStore + where TContext : DbContext { - /// - /// Provides methods allowing to manage the applications stored in a database. - /// - /// The type of the Entity Framework database context. - public class OpenIddictEntityFrameworkCoreApplicationStore : - OpenIddictEntityFrameworkCoreApplicationStore - where TContext : DbContext + public OpenIddictEntityFrameworkCoreApplicationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) + { + } +} + +/// +/// Provides methods allowing to manage the applications stored in a database. +/// +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkCoreApplicationStore : + OpenIddictEntityFrameworkCoreApplicationStore, + OpenIddictEntityFrameworkCoreAuthorization, + OpenIddictEntityFrameworkCoreToken, TContext, TKey> + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkCoreApplicationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) { - public OpenIddictEntityFrameworkCoreApplicationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) - { - } } +} + +/// +/// Provides methods allowing to manage the applications stored in a database. +/// +/// The type of the Application entity. +/// The type of the Authorization entity. +/// The type of the Token entity. +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkCoreApplicationStore : IOpenIddictApplicationStore + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TToken : OpenIddictEntityFrameworkCoreToken + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkCoreApplicationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + { + Cache = cache; + Context = context; + Options = options; + } + + /// + /// Gets the memory cache associated with the current store. + /// + protected IMemoryCache Cache { get; } + + /// + /// Gets the database context associated with the current store. + /// + protected TContext Context { get; } + + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Applications => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Authorizations => Context.Set(); /// - /// Provides methods allowing to manage the applications stored in a database. + /// Gets the database set corresponding to the entity. /// - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkCoreApplicationStore : - OpenIddictEntityFrameworkCoreApplicationStore, - OpenIddictEntityFrameworkCoreAuthorization, - OpenIddictEntityFrameworkCoreToken, TContext, TKey> - where TContext : DbContext - where TKey : notnull, IEquatable + private DbSet Tokens => Context.Set(); + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + => await Applications.AsQueryable().LongCountAsync(cancellationToken); + + /// + public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkCoreApplicationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) + if (query is null) { + throw new ArgumentNullException(nameof(query)); } + + return await query(Applications).LongCountAsync(cancellationToken); } - /// - /// Provides methods allowing to manage the applications stored in a database. - /// - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Token entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkCoreApplicationStore : IOpenIddictApplicationStore - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TToken : OpenIddictEntityFrameworkCoreToken - where TContext : DbContext - where TKey : notnull, IEquatable + /// + public virtual async ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkCoreApplicationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - { - Cache = cache; - Context = context; - Options = options; - } - - /// - /// Gets the memory cache associated with the current store. - /// - protected IMemoryCache Cache { get; } - - /// - /// Gets the database context associated with the current store. - /// - protected TContext Context { get; } - - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Applications => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Authorizations => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Tokens => Context.Set(); - - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) - => await Applications.AsQueryable().LongCountAsync(cancellationToken); - - /// - public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return await query(Applications).LongCountAsync(cancellationToken); + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual async ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Context.Add(application); - Context.Add(application); + await Context.SaveChangesAsync(cancellationToken); + } - await Context.SaveChangesAsync(cancellationToken); + /// + public virtual async ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual async ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken) + async ValueTask CreateTransactionAsync() { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - async ValueTask CreateTransactionAsync() - { - // Note: transactions that specify an explicit isolation level are only supported by - // relational providers and trying to use them with a different provider results in - // an invalid operation exception being thrown at runtime. To prevent that, a manual - // check is made to ensure the underlying transaction manager is relational. - var manager = Context.Database.GetService(); - if (manager is IRelationalTransactionManager) + // Note: transactions that specify an explicit isolation level are only supported by + // relational providers and trying to use them with a different provider results in + // an invalid operation exception being thrown at runtime. To prevent that, a manual + // check is made to ensure the underlying transaction manager is relational. + var manager = Context.Database.GetService(); + if (manager is IRelationalTransactionManager) + { + try { - try - { - return await Context.Database.BeginTransactionAsync(IsolationLevel.Serializable, cancellationToken); - } - - catch - { - return null; - } + return await Context.Database.BeginTransactionAsync(IsolationLevel.Serializable, cancellationToken); } - return null; - } - - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be - // filtered using authorization.Application.Id.Equals(key). To work around this issue, - // this local method uses an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - Task> ListAuthorizationsAsync() - => (from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking() - join element in Applications.AsTracking() on authorization.Application!.Id equals element.Id - where element.Id!.Equals(application.Id) - select authorization).ToListAsync(cancellationToken); - - // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be - // filtered using token.Application.Id.Equals(key). To work around this issue, - // this local method uses an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - Task> ListTokensAsync() - => (from token in Tokens.AsTracking() - where token.Authorization == null - join element in Applications.AsTracking() on token.Application!.Id equals element.Id - where element.Id!.Equals(application.Id) - select token).ToListAsync(cancellationToken); - - // To prevent an SQL exception from being thrown if a new associated entity is - // created after the existing entries have been listed, the following logic is - // executed in a serializable transaction, that will lock the affected tables. - using var transaction = await CreateTransactionAsync(); - - // Remove all the authorizations associated with the application and - // the tokens attached to these implicit or explicit authorizations. - var authorizations = await ListAuthorizationsAsync(); - foreach (var authorization in authorizations) - { - foreach (var token in authorization.Tokens) + catch { - Context.Remove(token); + return null; } - - Context.Remove(authorization); } - // Remove all the tokens associated with the application. - var tokens = await ListTokensAsync(); - foreach (var token in tokens) + return null; + } + + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be + // filtered using authorization.Application.Id.Equals(key). To work around this issue, + // this local method uses an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + Task> ListAuthorizationsAsync() + => (from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking() + join element in Applications.AsTracking() on authorization.Application!.Id equals element.Id + where element.Id!.Equals(application.Id) + select authorization).ToListAsync(cancellationToken); + + // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be + // filtered using token.Application.Id.Equals(key). To work around this issue, + // this local method uses an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + Task> ListTokensAsync() + => (from token in Tokens.AsTracking() + where token.Authorization == null + join element in Applications.AsTracking() on token.Application!.Id equals element.Id + where element.Id!.Equals(application.Id) + select token).ToListAsync(cancellationToken); + + // To prevent an SQL exception from being thrown if a new associated entity is + // created after the existing entries have been listed, the following logic is + // executed in a serializable transaction, that will lock the affected tables. + using var transaction = await CreateTransactionAsync(); + + // Remove all the authorizations associated with the application and + // the tokens attached to these implicit or explicit authorizations. + var authorizations = await ListAuthorizationsAsync(); + foreach (var authorization in authorizations) + { + foreach (var token in authorization.Tokens) { Context.Remove(token); } - Context.Remove(application); + Context.Remove(authorization); + } - try - { - await Context.SaveChangesAsync(cancellationToken); - transaction?.Commit(); - } + // Remove all the tokens associated with the application. + var tokens = await ListTokensAsync(); + foreach (var token in tokens) + { + Context.Remove(token); + } - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(application).State = EntityState.Unchanged; + Context.Remove(application); - foreach (var authorization in authorizations) - { - Context.Entry(authorization).State = EntityState.Unchanged; - } + try + { + await Context.SaveChangesAsync(cancellationToken); + transaction?.Commit(); + } - foreach (var token in tokens) - { - Context.Entry(token).State = EntityState.Unchanged; - } + catch (DbUpdateConcurrencyException exception) + { + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(application).State = EntityState.Unchanged; - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239), exception); + foreach (var authorization in authorizations) + { + Context.Entry(authorization).State = EntityState.Unchanged; } - } - /// - public virtual async ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) + foreach (var token in tokens) { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + Context.Entry(token).State = EntityState.Unchanged; } - return await (from application in Applications.AsTracking() - where application.ClientId == identifier - select application).FirstOrDefaultAsync(cancellationToken); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239), exception); } + } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + /// + public virtual async ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - var key = ConvertIdentifierFromString(identifier); + return await (from application in Applications.AsTracking() + where application.ClientId == identifier + select application).FirstOrDefaultAsync(cancellationToken); + } - return await (from application in Applications.AsTracking() - where application.Id!.Equals(key) - select application).FirstOrDefaultAsync(cancellationToken); + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual IAsyncEnumerable FindByPostLogoutRedirectUriAsync( - string address, CancellationToken cancellationToken) + var key = ConvertIdentifierFromString(identifier); + + return await (from application in Applications.AsTracking() + where application.Id!.Equals(key) + select application).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindByPostLogoutRedirectUriAsync( + string address, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(address)) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } - // To optimize the efficiency of the query a bit, only applications whose stringified - // PostLogoutRedirectUris contains the specified URL are returned. Once the applications - // are retrieved, a second pass is made to ensure only valid elements are returned. - // Implementers that use this method in a hot path may want to override this method - // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. + // To optimize the efficiency of the query a bit, only applications whose stringified + // PostLogoutRedirectUris contains the specified URL are returned. Once the applications + // are retrieved, a second pass is made to ensure only valid elements are returned. + // Implementers that use this method in a hot path may want to override this method + // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var applications = (from application in Applications.AsTracking() - where application.PostLogoutRedirectUris!.Contains(address) - select application).AsAsyncEnumerable(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var applications = (from application in Applications.AsTracking() + where application.PostLogoutRedirectUris!.Contains(address) + select application).AsAsyncEnumerable(cancellationToken); - await foreach (var application in applications) + await foreach (var application in applications) + { + var addresses = await GetPostLogoutRedirectUrisAsync(application, cancellationToken); + if (addresses.Contains(address, StringComparer.Ordinal)) { - var addresses = await GetPostLogoutRedirectUrisAsync(application, cancellationToken); - if (addresses.Contains(address, StringComparer.Ordinal)) - { - yield return application; - } + yield return application; } } } + } - /// - public virtual IAsyncEnumerable FindByRedirectUriAsync( - string address, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindByRedirectUriAsync( + string address, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(address)) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } - // To optimize the efficiency of the query a bit, only applications whose stringified - // RedirectUris property contains the specified URL are returned. Once the applications - // are retrieved, a second pass is made to ensure only valid elements are returned. - // Implementers that use this method in a hot path may want to override this method - // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. + // To optimize the efficiency of the query a bit, only applications whose stringified + // RedirectUris property contains the specified URL are returned. Once the applications + // are retrieved, a second pass is made to ensure only valid elements are returned. + // Implementers that use this method in a hot path may want to override this method + // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var applications = (from application in Applications.AsTracking() - where application.RedirectUris!.Contains(address) - select application).AsAsyncEnumerable(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var applications = (from application in Applications.AsTracking() + where application.RedirectUris!.Contains(address) + select application).AsAsyncEnumerable(cancellationToken); - await foreach (var application in applications) + await foreach (var application in applications) + { + var addresses = await GetRedirectUrisAsync(application, cancellationToken); + if (addresses.Contains(address, StringComparer.Ordinal)) { - var addresses = await GetRedirectUrisAsync(application, cancellationToken); - if (addresses.Contains(address, StringComparer.Ordinal)) - { - yield return application; - } + yield return application; } } } + } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return await query(Applications.AsTracking(), state).FirstOrDefaultAsync(cancellationToken); + throw new ArgumentNullException(nameof(query)); } - /// - public virtual ValueTask GetClientIdAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return await query(Applications.AsTracking(), state).FirstOrDefaultAsync(cancellationToken); + } - return new ValueTask(application.ClientId); + /// + public virtual ValueTask GetClientIdAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetClientSecretAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask(application.ClientId); + } - return new ValueTask(application.ClientSecret); + /// + public virtual ValueTask GetClientSecretAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask(application.ClientSecret); + } - return new ValueTask(application.Type); + /// + public virtual ValueTask GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetConsentTypeAsync(TApplication application, CancellationToken cancellationToken) + return new ValueTask(application.Type); + } + + /// + public virtual ValueTask GetConsentTypeAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask(application.ConsentType); + return new ValueTask(application.ConsentType); + } + + /// + public virtual ValueTask GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) + return new ValueTask(application.DisplayName); + } + + /// + public virtual ValueTask> GetDisplayNamesAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask(application.DisplayName); + if (string.IsNullOrEmpty(application.DisplayNames)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetDisplayNamesAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified display names is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("7762c378-c113-4564-b14b-1402b3949aaa", "\x1e", application.DisplayNames); + var names = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.DisplayNames)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(application.DisplayNames); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified display names is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("7762c378-c113-4564-b14b-1402b3949aaa", "\x1e", application.DisplayNames); - var names = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.DisplayNames); - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var property in document.RootElement.EnumerateObject()) + var value = property.Value.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = property.Value.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder[CultureInfo.GetCultureInfo(property.Name)] = value; + continue; } - return builder.ToImmutable(); - }); + builder[CultureInfo.GetCultureInfo(property.Name)] = value; + } + + return builder.ToImmutable(); + }); + + return new ValueTask>(names); + } - return new ValueTask>(names); + /// + public virtual ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken) + return new ValueTask(ConvertIdentifierToString(application.Id)); + } + + /// + public virtual ValueTask> GetPermissionsAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask(ConvertIdentifierToString(application.Id)); + if (string.IsNullOrEmpty(application.Permissions)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetPermissionsAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified permissions is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions); + var permissions = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.Permissions)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(application.Permissions); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified permissions is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions); - var permissions = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) + { + continue; + } - using var document = JsonDocument.Parse(application.Permissions); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); + builder.Add(value); + } - foreach (var element in document.RootElement.EnumerateArray()) - { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } + return builder.ToImmutable(); + }); - builder.Add(value); - } + return new ValueTask>(permissions); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetPostLogoutRedirectUrisAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask>(permissions); + if (string.IsNullOrEmpty(application.PostLogoutRedirectUris)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetPostLogoutRedirectUrisAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified addresses is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris); + var addresses = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.PostLogoutRedirectUris)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(application.PostLogoutRedirectUris); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified addresses is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris); - var addresses = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.PostLogoutRedirectUris); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder.Add(value); + continue; } - return builder.ToImmutable(); - }); - - return new ValueTask>(addresses); - } - - /// - public virtual ValueTask> GetPropertiesAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); + builder.Add(value); } - if (string.IsNullOrEmpty(application.Properties)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + return builder.ToImmutable(); + }); - // Note: parsing the stringified properties is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("2e3e9680-5654-48d8-a27d-b8bb4f0f1d50", "\x1e", application.Properties); - var properties = Cache.GetOrCreate(key, entry => - { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.Properties); - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return new ValueTask>(addresses); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetPropertiesAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask>(properties); + if (string.IsNullOrEmpty(application.Properties)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetRedirectUrisAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified properties is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("2e3e9680-5654-48d8-a27d-b8bb4f0f1d50", "\x1e", application.Properties); + var properties = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.RedirectUris)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(application.Properties); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified addresses is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris); - var addresses = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.RedirectUris); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); + builder[property.Name] = property.Value.Clone(); + } - foreach (var element in document.RootElement.EnumerateArray()) - { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } + return builder.ToImmutable(); + }); - builder.Add(value); - } + return new ValueTask>(properties); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetRedirectUrisAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - return new ValueTask>(addresses); + if (string.IsNullOrEmpty(application.RedirectUris)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetRequirementsAsync(TApplication application, CancellationToken cancellationToken) + // Note: parsing the stringified addresses is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris); + var addresses = Cache.GetOrCreate(key, entry => { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(application.Requirements)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(application.RedirectUris); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified requirements is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("b4808a89-8969-4512-895f-a909c62a8995", "\x1e", application.Requirements); - var requirements = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(application.Requirements); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder.Add(value); + continue; } - return builder.ToImmutable(); - }); + builder.Add(value); + } - return new ValueTask>(requirements); - } + return builder.ToImmutable(); + }); - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + return new ValueTask>(addresses); + } + + /// + public virtual ValueTask> GetRequirementsAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - try - { - return new ValueTask(Activator.CreateInstance()); - } + throw new ArgumentNullException(nameof(application)); + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0240), exception))); - } + if (string.IsNullOrEmpty(application.Requirements)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + // Note: parsing the stringified requirements is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("b4808a89-8969-4512-895f-a909c62a8995", "\x1e", application.Requirements); + var requirements = Cache.GetOrCreate(key, entry => { - var query = Applications.AsQueryable().OrderBy(application => application.Id!).AsTracking(); + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } + using var document = JsonDocument.Parse(application.Requirements); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - if (count.HasValue) + foreach (var element in document.RootElement.EnumerateArray()) { - query = query.Take(count.Value); + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) + { + continue; + } + + builder.Add(value); } - return query.AsAsyncEnumerable(cancellationToken); - } + return builder.ToImmutable(); + }); - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + return new ValueTask>(requirements); + } - return query(Applications.AsTracking(), state).AsAsyncEnumerable(cancellationToken); + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try + { + return new ValueTask(Activator.CreateInstance()); } - /// - public virtual ValueTask SetClientIdAsync(TApplication application, string? identifier, CancellationToken cancellationToken) + catch (MemberAccessException exception) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0240), exception))); + } + } - application.ClientId = identifier; + /// + public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + { + var query = Applications.AsQueryable().OrderBy(application => application.Id!).AsTracking(); - return default; + if (offset.HasValue) + { + query = query.Skip(offset.Value); } - /// - public virtual ValueTask SetClientSecretAsync(TApplication application, string? secret, CancellationToken cancellationToken) + if (count.HasValue) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + query = query.Take(count.Value); + } - application.ClientSecret = secret; + return query.AsAsyncEnumerable(cancellationToken); + } - return default; + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual ValueTask SetClientTypeAsync(TApplication application, string? type, CancellationToken cancellationToken) + return query(Applications.AsTracking(), state).AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual ValueTask SetClientIdAsync(TApplication application, string? identifier, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - application.Type = type; + application.ClientId = identifier; - return default; - } + return default; + } - /// - public virtual ValueTask SetConsentTypeAsync(TApplication application, string? type, CancellationToken cancellationToken) + /// + public virtual ValueTask SetClientSecretAsync(TApplication application, string? secret, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - application.ConsentType = type; + application.ClientSecret = secret; - return default; - } + return default; + } - /// - public virtual ValueTask SetDisplayNameAsync(TApplication application, string? name, CancellationToken cancellationToken) + /// + public virtual ValueTask SetClientTypeAsync(TApplication application, string? type, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - application.DisplayName = name; + application.Type = type; - return default; - } + return default; + } - /// - public virtual ValueTask SetDisplayNamesAsync(TApplication application, - ImmutableDictionary names, CancellationToken cancellationToken) + /// + public virtual ValueTask SetConsentTypeAsync(TApplication application, string? type, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - if (names is null || names.IsEmpty) - { - application.DisplayNames = null; + application.ConsentType = type; - return default; - } + return default; + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + /// + public virtual ValueTask SetDisplayNameAsync(TApplication application, string? name, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - writer.WriteStartObject(); + application.DisplayName = name; - foreach (var pair in names) - { - writer.WritePropertyName(pair.Key.Name); - writer.WriteStringValue(pair.Value); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetDisplayNamesAsync(TApplication application, + ImmutableDictionary names, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.DisplayNames = Encoding.UTF8.GetString(stream.ToArray()); + if (names is null || names.IsEmpty) + { + application.DisplayNames = null; return default; } - /// - public virtual ValueTask SetPermissionsAsync(TApplication application, ImmutableArray permissions, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (permissions.IsDefaultOrEmpty) - { - application.Permissions = null; + writer.WriteStartObject(); - return default; - } + foreach (var pair in names) + { + writer.WritePropertyName(pair.Key.Name); + writer.WriteStringValue(pair.Value); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartArray(); + application.DisplayNames = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var permission in permissions) - { - writer.WriteStringValue(permission); - } + return default; + } - writer.WriteEndArray(); - writer.Flush(); + /// + public virtual ValueTask SetPermissionsAsync(TApplication application, ImmutableArray permissions, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.Permissions = Encoding.UTF8.GetString(stream.ToArray()); + if (permissions.IsDefaultOrEmpty) + { + application.Permissions = null; return default; } - /// - public virtual ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, - ImmutableArray addresses, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (addresses.IsDefaultOrEmpty) - { - application.PostLogoutRedirectUris = null; + writer.WriteStartArray(); - return default; - } + foreach (var permission in permissions) + { + writer.WriteStringValue(permission); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndArray(); + writer.Flush(); - writer.WriteStartArray(); + application.Permissions = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var address in addresses) - { - writer.WriteStringValue(address); - } + return default; + } - writer.WriteEndArray(); - writer.Flush(); + /// + public virtual ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, + ImmutableArray addresses, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.PostLogoutRedirectUris = Encoding.UTF8.GetString(stream.ToArray()); + if (addresses.IsDefaultOrEmpty) + { + application.PostLogoutRedirectUris = null; return default; } - /// - public virtual ValueTask SetPropertiesAsync(TApplication application, - ImmutableDictionary properties, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (properties is null || properties.IsEmpty) - { - application.Properties = null; + writer.WriteStartArray(); - return default; - } + foreach (var address in addresses) + { + writer.WriteStringValue(address); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndArray(); + writer.Flush(); - writer.WriteStartObject(); + application.PostLogoutRedirectUris = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetPropertiesAsync(TApplication application, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.Properties = Encoding.UTF8.GetString(stream.ToArray()); + if (properties is null || properties.IsEmpty) + { + application.Properties = null; return default; } - /// - public virtual ValueTask SetRedirectUrisAsync(TApplication application, - ImmutableArray addresses, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (addresses.IsDefaultOrEmpty) - { - application.RedirectUris = null; + writer.WriteStartObject(); - return default; - } + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartArray(); + application.Properties = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var address in addresses) - { - writer.WriteStringValue(address); - } + return default; + } - writer.WriteEndArray(); - writer.Flush(); + /// + public virtual ValueTask SetRedirectUrisAsync(TApplication application, + ImmutableArray addresses, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.RedirectUris = Encoding.UTF8.GetString(stream.ToArray()); + if (addresses.IsDefaultOrEmpty) + { + application.RedirectUris = null; return default; } - /// - public virtual ValueTask SetRequirementsAsync(TApplication application, ImmutableArray requirements, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (requirements.IsDefaultOrEmpty) - { - application.Requirements = null; + writer.WriteStartArray(); - return default; - } + foreach (var address in addresses) + { + writer.WriteStringValue(address); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndArray(); + writer.Flush(); - writer.WriteStartArray(); + application.RedirectUris = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var requirement in requirements) - { - writer.WriteStringValue(requirement); - } + return default; + } - writer.WriteEndArray(); - writer.Flush(); + /// + public virtual ValueTask SetRequirementsAsync(TApplication application, ImmutableArray requirements, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.Requirements = Encoding.UTF8.GetString(stream.ToArray()); + if (requirements.IsDefaultOrEmpty) + { + application.Requirements = null; return default; } - /// - public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - Context.Attach(application); + writer.WriteStartArray(); - // Generate a new concurrency token and attach it - // to the application before persisting the changes. - application.ConcurrencyToken = Guid.NewGuid().ToString(); + foreach (var requirement in requirements) + { + writer.WriteStringValue(requirement); + } - Context.Update(application); + writer.WriteEndArray(); + writer.Flush(); - try - { - await Context.SaveChangesAsync(cancellationToken); - } + application.Requirements = Encoding.UTF8.GetString(stream.ToArray()); - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(application).State = EntityState.Unchanged; + return default; + } - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239), exception); - } + /// + public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - /// Converts the provided identifier to a strongly typed key object. - /// - /// The identifier to convert. - /// An instance of representing the provided identifier. - public virtual TKey? ConvertIdentifierFromString(string? identifier) + Context.Attach(application); + + // Generate a new concurrency token and attach it + // to the application before persisting the changes. + application.ConcurrencyToken = Guid.NewGuid().ToString(); + + Context.Update(application); + + try { - if (string.IsNullOrEmpty(identifier)) - { - return default; - } + await Context.SaveChangesAsync(cancellationToken); + } + + catch (DbUpdateConcurrencyException exception) + { + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(application).State = EntityState.Unchanged; - return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239), exception); } + } - /// - /// Converts the provided identifier to its string representation. - /// - /// The identifier to convert. - /// A representation of the provided identifier. - public virtual string? ConvertIdentifierToString(TKey? identifier) + /// + /// Converts the provided identifier to a strongly typed key object. + /// + /// The identifier to convert. + /// An instance of representing the provided identifier. + public virtual TKey? ConvertIdentifierFromString(string? identifier) + { + if (string.IsNullOrEmpty(identifier)) { - if (Equals(identifier, default(TKey))) - { - return null; - } + return default; + } + + return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + } - return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); + /// + /// Converts the provided identifier to its string representation. + /// + /// The identifier to convert. + /// A representation of the provided identifier. + public virtual string? ConvertIdentifierToString(TKey? identifier) + { + if (Equals(identifier, default(TKey))) + { + return null; } + + return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs index 659fbf4c..fdaab970 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs @@ -27,294 +27,340 @@ using OpenIddict.EntityFrameworkCore.Models; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Provides methods allowing to manage the authorizations stored in a database. +/// +/// The type of the Entity Framework database context. +public class OpenIddictEntityFrameworkCoreAuthorizationStore : + OpenIddictEntityFrameworkCoreAuthorizationStore + where TContext : DbContext { + public OpenIddictEntityFrameworkCoreAuthorizationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) + { + } +} + +/// +/// Provides methods allowing to manage the authorizations stored in a database. +/// +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkCoreAuthorizationStore : + OpenIddictEntityFrameworkCoreAuthorizationStore, + OpenIddictEntityFrameworkCoreApplication, + OpenIddictEntityFrameworkCoreToken, TContext, TKey> + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkCoreAuthorizationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) + { + } +} + +/// +/// Provides methods allowing to manage the authorizations stored in a database. +/// +/// The type of the Authorization entity. +/// The type of the Application entity. +/// The type of the Token entity. +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkCoreAuthorizationStore : IOpenIddictAuthorizationStore + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TToken : OpenIddictEntityFrameworkCoreToken + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkCoreAuthorizationStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + { + Cache = cache; + Context = context; + Options = options; + } + + /// + /// Gets the memory cache associated with the current store. + /// + protected IMemoryCache Cache { get; } + + /// + /// Gets the database context associated with the current store. + /// + protected TContext Context { get; } + /// - /// Provides methods allowing to manage the authorizations stored in a database. + /// Gets the options associated with the current store. /// - /// The type of the Entity Framework database context. - public class OpenIddictEntityFrameworkCoreAuthorizationStore : - OpenIddictEntityFrameworkCoreAuthorizationStore - where TContext : DbContext + protected IOptionsMonitor Options { get; } + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Applications => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Authorizations => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Tokens => Context.Set(); + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + => await Authorizations.AsQueryable().LongCountAsync(cancellationToken); + + /// + public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkCoreAuthorizationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) + if (query is null) { + throw new ArgumentNullException(nameof(query)); } + + return await query(Authorizations).LongCountAsync(cancellationToken); } - /// - /// Provides methods allowing to manage the authorizations stored in a database. - /// - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkCoreAuthorizationStore : - OpenIddictEntityFrameworkCoreAuthorizationStore, - OpenIddictEntityFrameworkCoreApplication, - OpenIddictEntityFrameworkCoreToken, TContext, TKey> - where TContext : DbContext - where TKey : notnull, IEquatable + /// + public virtual async ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkCoreAuthorizationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) + if (authorization is null) { + throw new ArgumentNullException(nameof(authorization)); } + + Context.Add(authorization); + + await Context.SaveChangesAsync(cancellationToken); } - /// - /// Provides methods allowing to manage the authorizations stored in a database. - /// - /// The type of the Authorization entity. - /// The type of the Application entity. - /// The type of the Token entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkCoreAuthorizationStore : IOpenIddictAuthorizationStore - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TToken : OpenIddictEntityFrameworkCoreToken - where TContext : DbContext - where TKey : notnull, IEquatable - { - public OpenIddictEntityFrameworkCoreAuthorizationStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - { - Cache = cache; - Context = context; - Options = options; - } - - /// - /// Gets the memory cache associated with the current store. - /// - protected IMemoryCache Cache { get; } - - /// - /// Gets the database context associated with the current store. - /// - protected TContext Context { get; } - - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Applications => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Authorizations => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Tokens => Context.Set(); - - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) - => await Authorizations.AsQueryable().LongCountAsync(cancellationToken); - - /// - public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - { - if (query is null) + /// + public virtual async ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + async ValueTask CreateTransactionAsync() + { + // Note: transactions that specify an explicit isolation level are only supported by + // relational providers and trying to use them with a different provider results in + // an invalid operation exception being thrown at runtime. To prevent that, a manual + // check is made to ensure the underlying transaction manager is relational. + var manager = Context.Database.GetService(); + if (manager is IRelationalTransactionManager) { - throw new ArgumentNullException(nameof(query)); + try + { + return await Context.Database.BeginTransactionAsync(IsolationLevel.Serializable, cancellationToken); + } + + catch + { + return null; + } } - return await query(Authorizations).LongCountAsync(cancellationToken); + return null; } - /// - public virtual async ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken) + // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be + // filtered using token.Application.Id.Equals(key). To work around this issue, + // this local method uses an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + Task> ListTokensAsync() + => (from token in Tokens.AsTracking() + join element in Authorizations.AsTracking() on token.Authorization!.Id equals element.Id + where element.Id!.Equals(authorization.Id) + select token).ToListAsync(cancellationToken); + + // To prevent an SQL exception from being thrown if a new associated entity is + // created after the existing entries have been listed, the following logic is + // executed in a serializable transaction, that will lock the affected tables. + using var transaction = await CreateTransactionAsync(); + + // Remove all the tokens associated with the authorization. + var tokens = await ListTokensAsync(); + foreach (var token in tokens) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + Context.Remove(token); + } - Context.Add(authorization); + Context.Remove(authorization); + try + { await Context.SaveChangesAsync(cancellationToken); + transaction?.Commit(); } - /// - public virtual async ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken) + catch (DbUpdateConcurrencyException exception) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(authorization).State = EntityState.Unchanged; - async ValueTask CreateTransactionAsync() + foreach (var token in tokens) { - // Note: transactions that specify an explicit isolation level are only supported by - // relational providers and trying to use them with a different provider results in - // an invalid operation exception being thrown at runtime. To prevent that, a manual - // check is made to ensure the underlying transaction manager is relational. - var manager = Context.Database.GetService(); - if (manager is IRelationalTransactionManager) - { - try - { - return await Context.Database.BeginTransactionAsync(IsolationLevel.Serializable, cancellationToken); - } - - catch - { - return null; - } - } - - return null; + Context.Entry(token).State = EntityState.Unchanged; } - // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be - // filtered using token.Application.Id.Equals(key). To work around this issue, - // this local method uses an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - Task> ListTokensAsync() - => (from token in Tokens.AsTracking() - join element in Authorizations.AsTracking() on token.Authorization!.Id equals element.Id - where element.Id!.Equals(authorization.Id) - select token).ToListAsync(cancellationToken); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241), exception); + } + } - // To prevent an SQL exception from being thrown if a new associated entity is - // created after the existing entries have been listed, the following logic is - // executed in a serializable transaction, that will lock the affected tables. - using var transaction = await CreateTransactionAsync(); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - // Remove all the tokens associated with the authorization. - var tokens = await ListTokensAsync(); - foreach (var token in tokens) - { - Context.Remove(token); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - Context.Remove(authorization); + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be + // filtered using authorization.Application.Id.Equals(key). To work around this issue, + // this method is overriden to use an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - try - { - await Context.SaveChangesAsync(cancellationToken); - transaction?.Commit(); - } + var key = ConvertIdentifierFromString(client); - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(authorization).State = EntityState.Unchanged; + return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Subject == subject + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where application.Id!.Equals(key) + select authorization).AsAsyncEnumerable(cancellationToken); + } - foreach (var token in tokens) - { - Context.Entry(token).State = EntityState.Unchanged; - } + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241), exception); - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(status)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be + // filtered using authorization.Application.Id.Equals(key). To work around this issue, + // this method is overriden to use an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be - // filtered using authorization.Application.Id.Equals(key). To work around this issue, - // this method is overriden to use an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Subject == subject && authorization.Status == status + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where application.Id!.Equals(key) + select authorization).AsAsyncEnumerable(cancellationToken); + } - return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Subject == subject - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be - // filtered using authorization.Application.Id.Equals(key). To work around this issue, - // this method is overriden to use an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be + // filtered using authorization.Application.Id.Equals(key). To work around this issue, + // this method is overriden to use an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - var key = ConvertIdentifierFromString(client); + var key = ConvertIdentifierFromString(client); + + return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Subject == subject && + authorization.Status == status && + authorization.Type == type + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where application.Id!.Equals(key) + select authorization).AsAsyncEnumerable(cancellationToken); + } - return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Subject == subject && authorization.Status == status - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, + ImmutableArray scopes, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be // filtered using authorization.Application.Id.Equals(key). To work around this issue, // this method is overriden to use an explicit join before applying the equality check. @@ -322,678 +368,631 @@ namespace OpenIddict.EntityFrameworkCore var key = ConvertIdentifierFromString(client); - return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).AsAsyncEnumerable(cancellationToken); - } - - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } - - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + var authorizations = (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Subject == subject && + authorization.Status == status && + authorization.Type == type + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where application.Id!.Equals(key) + select authorization).AsAsyncEnumerable(cancellationToken); - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + await foreach (var authorization in authorizations) { - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be - // filtered using authorization.Application.Id.Equals(key). To work around this issue, - // this method is overriden to use an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - - var key = ConvertIdentifierFromString(client); - - var authorizations = (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(key) - select authorization).AsAsyncEnumerable(cancellationToken); - - await foreach (var authorization in authorizations) + if (new HashSet(await GetScopesAsync(authorization, cancellationToken), StringComparer.Ordinal).IsSupersetOf(scopes)) { - if (new HashSet(await GetScopesAsync(authorization, cancellationToken), StringComparer.Ordinal).IsSupersetOf(scopes)) - { - yield return authorization; - } + yield return authorization; } } } + } - /// - public virtual IAsyncEnumerable FindByApplicationIdAsync( - string identifier, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindByApplicationIdAsync( + string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be - // filtered using authorization.Application.Id.Equals(key). To work around this issue, - // this method is overriden to use an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be + // filtered using authorization.Application.Id.Equals(key). To work around this issue, + // this method is overriden to use an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - var key = ConvertIdentifierFromString(identifier); + var key = ConvertIdentifierFromString(identifier); + + return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where application.Id!.Equals(identifier) + select authorization).AsAsyncEnumerable(cancellationToken); + } - return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id - where application.Id!.Equals(identifier) - select authorization).AsAsyncEnumerable(cancellationToken); + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + var key = ConvertIdentifierFromString(identifier); + + return await (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Id!.Equals(key) + select authorization).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindBySubjectAsync( + string subject, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - var key = ConvertIdentifierFromString(identifier); + return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Subject == subject + select authorization).AsAsyncEnumerable(cancellationToken); + } - return await (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Id!.Equals(key) - select authorization).FirstOrDefaultAsync(cancellationToken); + /// + public virtual async ValueTask GetApplicationIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual IAsyncEnumerable FindBySubjectAsync( - string subject, CancellationToken cancellationToken) + // If the application is not attached to the authorization, try to load it manually. + if (authorization.Application is null) { - if (string.IsNullOrEmpty(subject)) + var reference = Context.Entry(authorization).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + return null; } - return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() - where authorization.Subject == subject - select authorization).AsAsyncEnumerable(cancellationToken); + await reference.LoadAsync(cancellationToken); } - /// - public virtual async ValueTask GetApplicationIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (authorization.Application is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return null; + } - // If the application is not attached to the authorization, try to load it manually. - if (authorization.Application is null) - { - var reference = Context.Entry(authorization).Reference(entry => entry.Application); - if (reference.EntityEntry.State == EntityState.Detached) - { - return null; - } + return ConvertIdentifierToString(authorization.Application.Id); + } - await reference.LoadAsync(cancellationToken); - } + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } - if (authorization.Application is null) - { - return null; - } + return await query( + Authorizations.Include(authorization => authorization.Application) + .AsTracking(), state).FirstOrDefaultAsync(cancellationToken); + } - return ConvertIdentifierToString(authorization.Application.Id); + /// + public virtual ValueTask GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + if (authorization.CreationDate == null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return await query( - Authorizations.Include(authorization => authorization.Application) - .AsTracking(), state).FirstOrDefaultAsync(cancellationToken); + return new ValueTask(result: null); } - /// - public virtual ValueTask GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken) + return new ValueTask(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc)); + } + + /// + public virtual ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - if (authorization.CreationDate == null) - { - return new ValueTask(result: null); - } + return new ValueTask(ConvertIdentifierToString(authorization.Id)); + } - return new ValueTask(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask> GetPropertiesAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(authorization.Properties)) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - return new ValueTask(ConvertIdentifierToString(authorization.Id)); + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetPropertiesAsync(TAuthorization authorization, CancellationToken cancellationToken) + // Note: parsing the stringified properties is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("68056e1a-dbcf-412b-9a6a-d791c7dbe726", "\x1e", authorization.Properties); + var properties = Cache.GetOrCreate(key, entry => { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(authorization.Properties)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(authorization.Properties); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified properties is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("68056e1a-dbcf-412b-9a6a-d791c7dbe726", "\x1e", authorization.Properties); - var properties = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); + builder[property.Name] = property.Value.Clone(); + } - using var document = JsonDocument.Parse(authorization.Properties); - var builder = ImmutableDictionary.CreateBuilder(); + return builder.ToImmutable(); + }); - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return new ValueTask>(properties); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetScopesAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - return new ValueTask>(properties); + if (string.IsNullOrEmpty(authorization.Scopes)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetScopesAsync(TAuthorization authorization, CancellationToken cancellationToken) + // Note: parsing the stringified scopes is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("2ba4ab0f-e2ec-4d48-b3bd-28e2bb660c75", "\x1e", authorization.Scopes); + var scopes = Cache.GetOrCreate(key, entry => { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(authorization.Scopes)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(authorization.Scopes); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified scopes is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("2ba4ab0f-e2ec-4d48-b3bd-28e2bb660c75", "\x1e", authorization.Scopes); - var scopes = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(authorization.Scopes); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder.Add(value); + continue; } - return builder.ToImmutable(); - }); + builder.Add(value); + } - return new ValueTask>(scopes); - } + return builder.ToImmutable(); + }); - /// - public virtual ValueTask GetStatusAsync(TAuthorization authorization, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return new ValueTask>(scopes); + } - return new ValueTask(authorization.Status); + /// + public virtual ValueTask GetStatusAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask GetSubjectAsync(TAuthorization authorization, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return new ValueTask(authorization.Status); + } - return new ValueTask(authorization.Subject); + /// + public virtual ValueTask GetSubjectAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask GetTypeAsync(TAuthorization authorization, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return new ValueTask(authorization.Subject); + } - return new ValueTask(authorization.Type); + /// + public virtual ValueTask GetTypeAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) - { - try - { - return new ValueTask(Activator.CreateInstance()); - } + return new ValueTask(authorization.Type); + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0242), exception))); - } + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try + { + return new ValueTask(Activator.CreateInstance()); } - /// - public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + catch (MemberAccessException exception) { - var query = Authorizations.Include(authorization => authorization.Application) - .OrderBy(authorization => authorization.Id!) - .AsTracking(); - - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0242), exception))); + } + } - if (count.HasValue) - { - query = query.Take(count.Value); - } + /// + public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + { + var query = Authorizations.Include(authorization => authorization.Application) + .OrderBy(authorization => authorization.Id!) + .AsTracking(); - return query.AsAsyncEnumerable(cancellationToken); + if (offset.HasValue) + { + query = query.Skip(offset.Value); } - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + if (count.HasValue) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return query( - Authorizations.Include(authorization => authorization.Application) - .AsTracking(), state).AsAsyncEnumerable(cancellationToken); + query = query.Take(count.Value); } - /// - public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + return query.AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - // Note: Entity Framework Core doesn't support set-based deletes, which prevents removing - // entities in a single command without having to retrieve and materialize them first. - // To work around this limitation, entities are manually listed and deleted using a batch logic. + throw new ArgumentNullException(nameof(query)); + } - List? exceptions = null; + return query( + Authorizations.Include(authorization => authorization.Application) + .AsTracking(), state).AsAsyncEnumerable(cancellationToken); + } - async ValueTask CreateTransactionAsync() - { - // Note: transactions that specify an explicit isolation level are only supported by - // relational providers and trying to use them with a different provider results in - // an invalid operation exception being thrown at runtime. To prevent that, a manual - // check is made to ensure the underlying transaction manager is relational. - var manager = Context.Database.GetService(); - if (manager is IRelationalTransactionManager) - { - // Note: relational providers like Sqlite are known to lack proper support - // for repeatable read transactions. To ensure this method can be safely used - // with such providers, the database transaction is created in a try/catch block. - try - { - return await Context.Database.BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken); - } - - catch - { - return null; - } - } + /// + public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + { + // Note: Entity Framework Core doesn't support set-based deletes, which prevents removing + // entities in a single command without having to retrieve and materialize them first. + // To work around this limitation, entities are manually listed and deleted using a batch logic. - return null; - } + List? exceptions = null; - // Note: to avoid sending too many queries, the maximum number of elements - // that can be removed by a single call to PruneAsync() is deliberately limited. - for (var index = 0; index < 1_000; index++) + async ValueTask CreateTransactionAsync() + { + // Note: transactions that specify an explicit isolation level are only supported by + // relational providers and trying to use them with a different provider results in + // an invalid operation exception being thrown at runtime. To prevent that, a manual + // check is made to ensure the underlying transaction manager is relational. + var manager = Context.Database.GetService(); + if (manager is IRelationalTransactionManager) { - cancellationToken.ThrowIfCancellationRequested(); - - // To prevent concurrency exceptions from being thrown if an entry is modified - // after it was retrieved from the database, the following logic is executed in - // a repeatable read transaction, that will put a lock on the retrieved entries - // and thus prevent them from being concurrently modified outside this block. - using var transaction = await CreateTransactionAsync(); - - // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable - // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. - // To work around this limitation, the threshold represented as a DateTimeOffset - // instance is manually converted to a UTC DateTime instance outside the query. - var date = threshold.UtcDateTime; - - var authorizations = - await (from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking() - where authorization.CreationDate < date - where authorization.Status != Statuses.Valid || - (authorization.Type == AuthorizationTypes.AdHoc && !authorization.Tokens.Any()) - orderby authorization.Id - select authorization).Take(1_000).ToListAsync(cancellationToken); - - if (authorizations.Count == 0) - { - break; - } - - // Note: new tokens may be attached after the authorizations were retrieved - // from the database since the transaction level is deliberately limited to - // repeatable read instead of serializable for performance reasons). In this - // case, the operation will fail, which is considered an acceptable risk. - Context.RemoveRange(authorizations); - + // Note: relational providers like Sqlite are known to lack proper support + // for repeatable read transactions. To ensure this method can be safely used + // with such providers, the database transaction is created in a try/catch block. try { - await Context.SaveChangesAsync(cancellationToken); - transaction?.Commit(); + return await Context.Database.BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken); } - catch (Exception exception) + catch { - exceptions ??= new List(capacity: 1); - exceptions.Add(exception); + return null; } } - if (exceptions is not null) - { - throw new AggregateException(SR.GetResourceString(SR.ID0243), exceptions); - } + return null; } - /// - public virtual async ValueTask SetApplicationIdAsync(TAuthorization authorization, - string? identifier, CancellationToken cancellationToken) + // Note: to avoid sending too many queries, the maximum number of elements + // that can be removed by a single call to PruneAsync() is deliberately limited. + for (var index = 0; index < 1_000; index++) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + cancellationToken.ThrowIfCancellationRequested(); - if (!string.IsNullOrEmpty(identifier)) - { - var key = ConvertIdentifierFromString(identifier); - - // Warning: FindAsync() is deliberately not used to work around a breaking change introduced - // in Entity Framework Core 3.x (where a ValueTask instead of a Task is now returned). - var application = - await Applications.AsQueryable() - .AsTracking() - .FirstOrDefaultAsync(application => application.Id!.Equals(key), cancellationToken); + // To prevent concurrency exceptions from being thrown if an entry is modified + // after it was retrieved from the database, the following logic is executed in + // a repeatable read transaction, that will put a lock on the retrieved entries + // and thus prevent them from being concurrently modified outside this block. + using var transaction = await CreateTransactionAsync(); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0244)); - } + // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable + // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. + // To work around this limitation, the threshold represented as a DateTimeOffset + // instance is manually converted to a UTC DateTime instance outside the query. + var date = threshold.UtcDateTime; - authorization.Application = application; - } + var authorizations = + await (from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking() + where authorization.CreationDate < date + where authorization.Status != Statuses.Valid || + (authorization.Type == AuthorizationTypes.AdHoc && !authorization.Tokens.Any()) + orderby authorization.Id + select authorization).Take(1_000).ToListAsync(cancellationToken); - else + if (authorizations.Count == 0) { - // If the application is not attached to the authorization, try to load it manually. - if (authorization.Application is null) - { - var reference = Context.Entry(authorization).Reference(entry => entry.Application); - if (reference.EntityEntry.State == EntityState.Detached) - { - return; - } + break; + } - await reference.LoadAsync(cancellationToken); - } + // Note: new tokens may be attached after the authorizations were retrieved + // from the database since the transaction level is deliberately limited to + // repeatable read instead of serializable for performance reasons). In this + // case, the operation will fail, which is considered an acceptable risk. + Context.RemoveRange(authorizations); - authorization.Application = null; + try + { + await Context.SaveChangesAsync(cancellationToken); + transaction?.Commit(); } - } - /// - public virtual ValueTask SetCreationDateAsync(TAuthorization authorization, - DateTimeOffset? date, CancellationToken cancellationToken) - { - if (authorization is null) + catch (Exception exception) { - throw new ArgumentNullException(nameof(authorization)); + exceptions ??= new List(capacity: 1); + exceptions.Add(exception); } + } - authorization.CreationDate = date?.UtcDateTime; + if (exceptions is not null) + { + throw new AggregateException(SR.GetResourceString(SR.ID0243), exceptions); + } + } - return default; + /// + public virtual async ValueTask SetApplicationIdAsync(TAuthorization authorization, + string? identifier, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask SetPropertiesAsync(TAuthorization authorization, - ImmutableDictionary properties, CancellationToken cancellationToken) + if (!string.IsNullOrEmpty(identifier)) { - if (authorization is null) + var key = ConvertIdentifierFromString(identifier); + + // Warning: FindAsync() is deliberately not used to work around a breaking change introduced + // in Entity Framework Core 3.x (where a ValueTask instead of a Task is now returned). + var application = + await Applications.AsQueryable() + .AsTracking() + .FirstOrDefaultAsync(application => application.Id!.Equals(key), cancellationToken); + + if (application is null) { - throw new ArgumentNullException(nameof(authorization)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0244)); } - if (properties is null || properties.IsEmpty) + authorization.Application = application; + } + + else + { + // If the application is not attached to the authorization, try to load it manually. + if (authorization.Application is null) { - authorization.Properties = null; + var reference = Context.Entry(authorization).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) + { + return; + } - return default; + await reference.LoadAsync(cancellationToken); } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + authorization.Application = null; + } + } - writer.WriteStartObject(); + /// + public virtual ValueTask SetCreationDateAsync(TAuthorization authorization, + DateTimeOffset? date, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + authorization.CreationDate = date?.UtcDateTime; - writer.WriteEndObject(); - writer.Flush(); + return default; + } - authorization.Properties = Encoding.UTF8.GetString(stream.ToArray()); + /// + public virtual ValueTask SetPropertiesAsync(TAuthorization authorization, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } + + if (properties is null || properties.IsEmpty) + { + authorization.Properties = null; return default; } - /// - public virtual ValueTask SetScopesAsync(TAuthorization authorization, - ImmutableArray scopes, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (scopes.IsDefaultOrEmpty) - { - authorization.Scopes = null; + writer.WriteStartObject(); - return default; - } + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartArray(); + authorization.Properties = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var scope in scopes) - { - writer.WriteStringValue(scope); - } + return default; + } - writer.WriteEndArray(); - writer.Flush(); + /// + public virtual ValueTask SetScopesAsync(TAuthorization authorization, + ImmutableArray scopes, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - authorization.Scopes = Encoding.UTF8.GetString(stream.ToArray()); + if (scopes.IsDefaultOrEmpty) + { + authorization.Scopes = null; return default; } - /// - public virtual ValueTask SetStatusAsync(TAuthorization authorization, - string? status, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - authorization.Status = status; + writer.WriteStartArray(); - return default; + foreach (var scope in scopes) + { + writer.WriteStringValue(scope); } - /// - public virtual ValueTask SetSubjectAsync(TAuthorization authorization, - string? subject, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + writer.WriteEndArray(); + writer.Flush(); - authorization.Subject = subject; + authorization.Scopes = Encoding.UTF8.GetString(stream.ToArray()); - return default; - } + return default; + } - /// - public virtual ValueTask SetTypeAsync(TAuthorization authorization, - string? type, CancellationToken cancellationToken) + /// + public virtual ValueTask SetStatusAsync(TAuthorization authorization, + string? status, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - authorization.Type = type; + authorization.Status = status; - return default; + return default; + } + + /// + public virtual ValueTask SetSubjectAsync(TAuthorization authorization, + string? subject, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual async ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken) + authorization.Subject = subject; + + return default; + } + + /// + public virtual ValueTask SetTypeAsync(TAuthorization authorization, + string? type, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - Context.Attach(authorization); + authorization.Type = type; - // Generate a new concurrency token and attach it - // to the authorization before persisting the changes. - authorization.ConcurrencyToken = Guid.NewGuid().ToString(); + return default; + } - Context.Update(authorization); + /// + public virtual async ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - try - { - await Context.SaveChangesAsync(cancellationToken); - } + Context.Attach(authorization); - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(authorization).State = EntityState.Unchanged; + // Generate a new concurrency token and attach it + // to the authorization before persisting the changes. + authorization.ConcurrencyToken = Guid.NewGuid().ToString(); - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241), exception); - } + Context.Update(authorization); + + try + { + await Context.SaveChangesAsync(cancellationToken); } - /// - /// Converts the provided identifier to a strongly typed key object. - /// - /// The identifier to convert. - /// An instance of representing the provided identifier. - public virtual TKey? ConvertIdentifierFromString(string? identifier) + catch (DbUpdateConcurrencyException exception) { - if (string.IsNullOrEmpty(identifier)) - { - return default; - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(authorization).State = EntityState.Unchanged; - return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241), exception); } + } - /// - /// Converts the provided identifier to its string representation. - /// - /// The identifier to convert. - /// A representation of the provided identifier. - public virtual string? ConvertIdentifierToString(TKey? identifier) + /// + /// Converts the provided identifier to a strongly typed key object. + /// + /// The identifier to convert. + /// An instance of representing the provided identifier. + public virtual TKey? ConvertIdentifierFromString(string? identifier) + { + if (string.IsNullOrEmpty(identifier)) { - if (Equals(identifier, default(TKey))) - { - return null; - } + return default; + } - return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); + return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + } + + /// + /// Converts the provided identifier to its string representation. + /// + /// The identifier to convert. + /// A representation of the provided identifier. + public virtual string? ConvertIdentifierToString(TKey? identifier) + { + if (Equals(identifier, default(TKey))) + { + return null; } + + return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs index 56da7746..587cb5ee 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs @@ -24,725 +24,724 @@ using OpenIddict.Abstractions; using OpenIddict.EntityFrameworkCore.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Provides methods allowing to manage the scopes stored in a database. +/// +/// The type of the Entity Framework database context. +public class OpenIddictEntityFrameworkCoreScopeStore : OpenIddictEntityFrameworkCoreScopeStore + where TContext : DbContext { + public OpenIddictEntityFrameworkCoreScopeStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) + { + } +} + +/// +/// Provides methods allowing to manage the scopes stored in a database. +/// +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkCoreScopeStore : OpenIddictEntityFrameworkCoreScopeStore, TContext, TKey> + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkCoreScopeStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) + { + } +} + +/// +/// Provides methods allowing to manage the scopes stored in a database. +/// +/// The type of the Scope entity. +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkCoreScopeStore : IOpenIddictScopeStore + where TScope : OpenIddictEntityFrameworkCoreScope + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkCoreScopeStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + { + Cache = cache; + Context = context; + Options = options; + } + /// - /// Provides methods allowing to manage the scopes stored in a database. + /// Gets the memory cache associated with the current store. /// - /// The type of the Entity Framework database context. - public class OpenIddictEntityFrameworkCoreScopeStore : OpenIddictEntityFrameworkCoreScopeStore - where TContext : DbContext + protected IMemoryCache Cache { get; } + + /// + /// Gets the database context associated with the current store. + /// + protected TContext Context { get; } + + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Scopes => Context.Set(); + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + => await Scopes.AsQueryable().LongCountAsync(cancellationToken); + + /// + public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkCoreScopeStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) + if (query is null) { + throw new ArgumentNullException(nameof(query)); } + + return await query(Scopes).LongCountAsync(cancellationToken); } - /// - /// Provides methods allowing to manage the scopes stored in a database. - /// - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkCoreScopeStore : OpenIddictEntityFrameworkCoreScopeStore, TContext, TKey> - where TContext : DbContext - where TKey : notnull, IEquatable + /// + public virtual async ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkCoreScopeStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) + if (scope is null) { + throw new ArgumentNullException(nameof(scope)); } - } - /// - /// Provides methods allowing to manage the scopes stored in a database. - /// - /// The type of the Scope entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkCoreScopeStore : IOpenIddictScopeStore - where TScope : OpenIddictEntityFrameworkCoreScope - where TContext : DbContext - where TKey : notnull, IEquatable - { - public OpenIddictEntityFrameworkCoreScopeStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - { - Cache = cache; - Context = context; - Options = options; - } - - /// - /// Gets the memory cache associated with the current store. - /// - protected IMemoryCache Cache { get; } - - /// - /// Gets the database context associated with the current store. - /// - protected TContext Context { get; } - - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Scopes => Context.Set(); - - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) - => await Scopes.AsQueryable().LongCountAsync(cancellationToken); - - /// - public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + Scopes.Add(scope); - return await query(Scopes).LongCountAsync(cancellationToken); - } + await Context.SaveChangesAsync(cancellationToken); + } - /// - public virtual async ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken) + /// + public virtual async ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - Scopes.Add(scope); + Context.Remove(scope); + try + { await Context.SaveChangesAsync(cancellationToken); } - /// - public virtual async ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken) + catch (DbUpdateConcurrencyException exception) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(scope).State = EntityState.Unchanged; - Context.Remove(scope); - - try - { - await Context.SaveChangesAsync(cancellationToken); - } - - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(scope).State = EntityState.Unchanged; - - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245), exception); - } + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245), exception); } + } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - var key = ConvertIdentifierFromString(identifier); + var key = ConvertIdentifierFromString(identifier); - return await (from scope in Scopes.AsTracking() - where scope.Id!.Equals(key) - select scope).FirstOrDefaultAsync(cancellationToken); - } + return await (from scope in Scopes.AsTracking() + where scope.Id!.Equals(key) + select scope).FirstOrDefaultAsync(cancellationToken); + } - /// - public virtual async ValueTask FindByNameAsync(string name, CancellationToken cancellationToken) + /// + public virtual async ValueTask FindByNameAsync(string name, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(name)) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); - } - - return await (from scope in Scopes.AsTracking() - where scope.Name == name - select scope).FirstOrDefaultAsync(cancellationToken); + throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); } - /// - public virtual IAsyncEnumerable FindByNamesAsync( - ImmutableArray names, CancellationToken cancellationToken) - { - if (names.Any(name => string.IsNullOrEmpty(name))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); - } + return await (from scope in Scopes.AsTracking() + where scope.Name == name + select scope).FirstOrDefaultAsync(cancellationToken); + } - // Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure - // ImmutableArray.Contains() (which is not fully supported by Entity Framework Core) is not used instead. - return (from scope in Scopes.AsTracking() - where Enumerable.Contains(names, scope.Name) - select scope).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindByNamesAsync( + ImmutableArray names, CancellationToken cancellationToken) + { + if (names.Any(name => string.IsNullOrEmpty(name))) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); } - /// - public virtual IAsyncEnumerable FindByResourceAsync( - string resource, CancellationToken cancellationToken) + // Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure + // ImmutableArray.Contains() (which is not fully supported by Entity Framework Core) is not used instead. + return (from scope in Scopes.AsTracking() + where Enumerable.Contains(names, scope.Name) + select scope).AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindByResourceAsync( + string resource, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(resource)) { - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); + } - // To optimize the efficiency of the query a bit, only scopes whose stringified - // Resources column contains the specified resource are returned. Once the scopes - // are retrieved, a second pass is made to ensure only valid elements are returned. - // Implementers that use this method in a hot path may want to override this method - // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. + // To optimize the efficiency of the query a bit, only scopes whose stringified + // Resources column contains the specified resource are returned. Once the scopes + // are retrieved, a second pass is made to ensure only valid elements are returned. + // Implementers that use this method in a hot path may want to override this method + // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var scopes = (from scope in Scopes.AsTracking() - where scope.Resources!.Contains(resource) - select scope).AsAsyncEnumerable(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var scopes = (from scope in Scopes.AsTracking() + where scope.Resources!.Contains(resource) + select scope).AsAsyncEnumerable(cancellationToken); - await foreach (var scope in scopes) + await foreach (var scope in scopes) + { + var resources = await GetResourcesAsync(scope, cancellationToken); + if (resources.Contains(resource, StringComparer.Ordinal)) { - var resources = await GetResourcesAsync(scope, cancellationToken); - if (resources.Contains(resource, StringComparer.Ordinal)) - { - yield return scope; - } + yield return scope; } } } + } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(query)); + } - return await query(Scopes.AsTracking(), state).FirstOrDefaultAsync(cancellationToken); + return await query(Scopes.AsTracking(), state).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken) + return new ValueTask(scope.Description); + } + + /// + public virtual ValueTask> GetDescriptionsAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - return new ValueTask(scope.Description); + if (string.IsNullOrEmpty(scope.Descriptions)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetDescriptionsAsync(TScope scope, CancellationToken cancellationToken) + // Note: parsing the stringified descriptions is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("42891062-8f69-43ba-9111-db7e8ded2553", "\x1e", scope.Descriptions); + var descriptions = Cache.GetOrCreate(key, entry => { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(scope.Descriptions)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(scope.Descriptions); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified descriptions is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("42891062-8f69-43ba-9111-db7e8ded2553", "\x1e", scope.Descriptions); - var descriptions = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(scope.Descriptions); - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var property in document.RootElement.EnumerateObject()) + var value = property.Value.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = property.Value.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder[CultureInfo.GetCultureInfo(property.Name)] = value; + continue; } - return builder.ToImmutable(); - }); + builder[CultureInfo.GetCultureInfo(property.Name)] = value; + } - return new ValueTask>(descriptions); + return builder.ToImmutable(); + }); + + return new ValueTask>(descriptions); + } + + /// + public virtual ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken) + return new ValueTask(scope.DisplayName); + } + + /// + public virtual ValueTask> GetDisplayNamesAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - return new ValueTask(scope.DisplayName); + if (string.IsNullOrEmpty(scope.DisplayNames)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetDisplayNamesAsync(TScope scope, CancellationToken cancellationToken) + // Note: parsing the stringified display names is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("e17d437b-bdd2-43f3-974e-46d524f4bae1", "\x1e", scope.DisplayNames); + var names = Cache.GetOrCreate(key, entry => { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(scope.DisplayNames)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(scope.DisplayNames); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified display names is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("e17d437b-bdd2-43f3-974e-46d524f4bae1", "\x1e", scope.DisplayNames); - var names = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(scope.DisplayNames); - var builder = ImmutableDictionary.CreateBuilder(); - - foreach (var property in document.RootElement.EnumerateObject()) + var value = property.Value.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = property.Value.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder[CultureInfo.GetCultureInfo(property.Name)] = value; + continue; } - return builder.ToImmutable(); - }); + builder[CultureInfo.GetCultureInfo(property.Name)] = value; + } - return new ValueTask>(names); - } + return builder.ToImmutable(); + }); + + return new ValueTask>(names); + } - /// - public virtual ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken) + /// + public virtual ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } + + return new ValueTask(ConvertIdentifierToString(scope.Id)); + } - return new ValueTask(ConvertIdentifierToString(scope.Id)); + /// + public virtual ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken) + return new ValueTask(scope.Name); + } + + /// + public virtual ValueTask> GetPropertiesAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - return new ValueTask(scope.Name); + if (string.IsNullOrEmpty(scope.Properties)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetPropertiesAsync(TScope scope, CancellationToken cancellationToken) + // Note: parsing the stringified properties is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("78d8dfdd-3870-442e-b62e-dc9bf6eaeff7", "\x1e", scope.Properties); + var properties = Cache.GetOrCreate(key, entry => { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(scope.Properties)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(scope.Properties); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified properties is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("78d8dfdd-3870-442e-b62e-dc9bf6eaeff7", "\x1e", scope.Properties); - var properties = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); + builder[property.Name] = property.Value.Clone(); + } - using var document = JsonDocument.Parse(scope.Properties); - var builder = ImmutableDictionary.CreateBuilder(); + return builder.ToImmutable(); + }); - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return new ValueTask>(properties); + } - return builder.ToImmutable(); - }); + /// + public virtual ValueTask> GetResourcesAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - return new ValueTask>(properties); + if (string.IsNullOrEmpty(scope.Resources)) + { + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetResourcesAsync(TScope scope, CancellationToken cancellationToken) + // Note: parsing the stringified resources is an expensive operation. + // To mitigate that, the resulting array is stored in the memory cache. + var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources); + var resources = Cache.GetOrCreate(key, entry => { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(scope.Resources)) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(scope.Resources); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - // Note: parsing the stringified resources is an expensive operation. - // To mitigate that, the resulting array is stored in the memory cache. - var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources); - var resources = Cache.GetOrCreate(key, entry => + foreach (var element in document.RootElement.EnumerateArray()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(scope.Resources); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - - foreach (var element in document.RootElement.EnumerateArray()) + var value = element.GetString(); + if (string.IsNullOrEmpty(value)) { - var value = element.GetString(); - if (string.IsNullOrEmpty(value)) - { - continue; - } - - builder.Add(value); + continue; } - return builder.ToImmutable(); - }); + builder.Add(value); + } - return new ValueTask>(resources); - } + return builder.ToImmutable(); + }); - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) - { - try - { - return new ValueTask(Activator.CreateInstance()); - } + return new ValueTask>(resources); + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0246), exception))); - } + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try + { + return new ValueTask(Activator.CreateInstance()); } - /// - public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + catch (MemberAccessException exception) { - var query = Scopes.AsQueryable().OrderBy(scope => scope.Id!).AsTracking(); + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0246), exception))); + } + } - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } + /// + public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + { + var query = Scopes.AsQueryable().OrderBy(scope => scope.Id!).AsTracking(); - if (count.HasValue) - { - query = query.Take(count.Value); - } + if (offset.HasValue) + { + query = query.Skip(offset.Value); + } - return query.AsAsyncEnumerable(cancellationToken); + if (count.HasValue) + { + query = query.Take(count.Value); } - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + return query.AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(query)); + } - return query(Scopes.AsTracking(), state).AsAsyncEnumerable(cancellationToken); + return query(Scopes.AsTracking(), state).AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual ValueTask SetDescriptionAsync(TScope scope, string? description, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask SetDescriptionAsync(TScope scope, string? description, CancellationToken cancellationToken) + scope.Description = description; + + return default; + } + + /// + public virtual ValueTask SetDescriptionsAsync(TScope scope, + ImmutableDictionary descriptions, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - scope.Description = description; + if (descriptions is null || descriptions.IsEmpty) + { + scope.Descriptions = null; return default; } - /// - public virtual ValueTask SetDescriptionsAsync(TScope scope, - ImmutableDictionary descriptions, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (descriptions is null || descriptions.IsEmpty) - { - scope.Descriptions = null; + writer.WriteStartObject(); - return default; - } + foreach (var description in descriptions) + { + writer.WritePropertyName(description.Key.Name); + writer.WriteStringValue(description.Value); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartObject(); + scope.Descriptions = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var description in descriptions) - { - writer.WritePropertyName(description.Key.Name); - writer.WriteStringValue(description.Value); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetDisplayNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - scope.Descriptions = Encoding.UTF8.GetString(stream.ToArray()); + scope.DisplayName = name; - return default; - } + return default; + } - /// - public virtual ValueTask SetDisplayNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + /// + public virtual ValueTask SetDisplayNamesAsync(TScope scope, + ImmutableDictionary names, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - scope.DisplayName = name; + if (names is null || names.IsEmpty) + { + scope.DisplayNames = null; return default; } - /// - public virtual ValueTask SetDisplayNamesAsync(TScope scope, - ImmutableDictionary names, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (names is null || names.IsEmpty) - { - scope.DisplayNames = null; + writer.WriteStartObject(); - return default; - } + foreach (var name in names) + { + writer.WritePropertyName(name.Key.Name); + writer.WriteStringValue(name.Value); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartObject(); + scope.DisplayNames = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var name in names) - { - writer.WritePropertyName(name.Key.Name); - writer.WriteStringValue(name.Value); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - scope.DisplayNames = Encoding.UTF8.GetString(stream.ToArray()); + scope.Name = name; - return default; - } + return default; + } - /// - public virtual ValueTask SetNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + /// + public virtual ValueTask SetPropertiesAsync(TScope scope, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - scope.Name = name; + if (properties is null || properties.IsEmpty) + { + scope.Properties = null; return default; } - /// - public virtual ValueTask SetPropertiesAsync(TScope scope, - ImmutableDictionary properties, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (properties is null || properties.IsEmpty) - { - scope.Properties = null; + writer.WriteStartObject(); - return default; - } + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteEndObject(); + writer.Flush(); - writer.WriteStartObject(); + scope.Properties = Encoding.UTF8.GetString(stream.ToArray()); - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetResourcesAsync(TScope scope, ImmutableArray resources, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } - scope.Properties = Encoding.UTF8.GetString(stream.ToArray()); + if (resources.IsDefaultOrEmpty) + { + scope.Resources = null; return default; } - /// - public virtual ValueTask SetResourcesAsync(TScope scope, ImmutableArray resources, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - if (resources.IsDefaultOrEmpty) - { - scope.Resources = null; - - return default; - } - - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - writer.WriteStartArray(); + writer.WriteStartArray(); - foreach (var resource in resources) - { - writer.WriteStringValue(resource); - } + foreach (var resource in resources) + { + writer.WriteStringValue(resource); + } - writer.WriteEndArray(); - writer.Flush(); + writer.WriteEndArray(); + writer.Flush(); - scope.Resources = Encoding.UTF8.GetString(stream.ToArray()); + scope.Resources = Encoding.UTF8.GetString(stream.ToArray()); - return default; - } + return default; + } - /// - public virtual async ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken) + /// + public virtual async ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - Context.Attach(scope); + throw new ArgumentNullException(nameof(scope)); + } - // Generate a new concurrency token and attach it - // to the scope before persisting the changes. - scope.ConcurrencyToken = Guid.NewGuid().ToString(); + Context.Attach(scope); - Context.Update(scope); + // Generate a new concurrency token and attach it + // to the scope before persisting the changes. + scope.ConcurrencyToken = Guid.NewGuid().ToString(); - try - { - await Context.SaveChangesAsync(cancellationToken); - } + Context.Update(scope); - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(scope).State = EntityState.Unchanged; - - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245), exception); - } + try + { + await Context.SaveChangesAsync(cancellationToken); } - /// - /// Converts the provided identifier to a strongly typed key object. - /// - /// The identifier to convert. - /// An instance of representing the provided identifier. - public virtual TKey? ConvertIdentifierFromString(string? identifier) + catch (DbUpdateConcurrencyException exception) { - if (string.IsNullOrEmpty(identifier)) - { - return default; - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(scope).State = EntityState.Unchanged; - return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245), exception); } + } - /// - /// Converts the provided identifier to its string representation. - /// - /// The identifier to convert. - /// A representation of the provided identifier. - public virtual string? ConvertIdentifierToString(TKey? identifier) + /// + /// Converts the provided identifier to a strongly typed key object. + /// + /// The identifier to convert. + /// An instance of representing the provided identifier. + public virtual TKey? ConvertIdentifierFromString(string? identifier) + { + if (string.IsNullOrEmpty(identifier)) { - if (Equals(identifier, default(TKey))) - { - return null; - } + return default; + } - return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); + return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + } + + /// + /// Converts the provided identifier to its string representation. + /// + /// The identifier to convert. + /// A representation of the provided identifier. + public virtual string? ConvertIdentifierToString(TKey? identifier) + { + if (Equals(identifier, default(TKey))) + { + return null; } + + return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); } -} \ No newline at end of file +} diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs index 6cf11725..45424861 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs @@ -26,992 +26,991 @@ using OpenIddict.EntityFrameworkCore.Models; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore +namespace OpenIddict.EntityFrameworkCore; + +/// +/// Provides methods allowing to manage the tokens stored in a database. +/// +/// The type of the Entity Framework database context. +public class OpenIddictEntityFrameworkCoreTokenStore : + OpenIddictEntityFrameworkCoreTokenStore + where TContext : DbContext { + public OpenIddictEntityFrameworkCoreTokenStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) + { + } +} + +/// +/// Provides methods allowing to manage the tokens stored in a database. +/// +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkCoreTokenStore : + OpenIddictEntityFrameworkCoreTokenStore, + OpenIddictEntityFrameworkCoreApplication, + OpenIddictEntityFrameworkCoreAuthorization, TContext, TKey> + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkCoreTokenStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + : base(cache, context, options) + { + } +} + +/// +/// Provides methods allowing to manage the tokens stored in a database. +/// +/// The type of the Token entity. +/// The type of the Application entity. +/// The type of the Authorization entity. +/// The type of the Entity Framework database context. +/// The type of the entity primary keys. +public class OpenIddictEntityFrameworkCoreTokenStore : IOpenIddictTokenStore + where TToken : OpenIddictEntityFrameworkCoreToken + where TApplication : OpenIddictEntityFrameworkCoreApplication + where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization + where TContext : DbContext + where TKey : notnull, IEquatable +{ + public OpenIddictEntityFrameworkCoreTokenStore( + IMemoryCache cache, + TContext context, + IOptionsMonitor options) + { + Cache = cache; + Context = context; + Options = options; + } + + /// + /// Gets the memory cache associated with the current store. + /// + protected IMemoryCache Cache { get; } + + /// + /// Gets the database context associated with the current store. + /// + protected TContext Context { get; } + + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Applications => Context.Set(); + /// - /// Provides methods allowing to manage the tokens stored in a database. + /// Gets the database set corresponding to the entity. /// - /// The type of the Entity Framework database context. - public class OpenIddictEntityFrameworkCoreTokenStore : - OpenIddictEntityFrameworkCoreTokenStore - where TContext : DbContext + private DbSet Authorizations => Context.Set(); + + /// + /// Gets the database set corresponding to the entity. + /// + private DbSet Tokens => Context.Set(); + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + => await Tokens.AsQueryable().LongCountAsync(cancellationToken); + + /// + public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkCoreTokenStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) + if (query is null) { + throw new ArgumentNullException(nameof(query)); } + + return await query(Tokens).LongCountAsync(cancellationToken); } - /// - /// Provides methods allowing to manage the tokens stored in a database. - /// - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkCoreTokenStore : - OpenIddictEntityFrameworkCoreTokenStore, - OpenIddictEntityFrameworkCoreApplication, - OpenIddictEntityFrameworkCoreAuthorization, TContext, TKey> - where TContext : DbContext - where TKey : notnull, IEquatable + /// + public virtual async ValueTask CreateAsync(TToken token, CancellationToken cancellationToken) { - public OpenIddictEntityFrameworkCoreTokenStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - : base(cache, context, options) + if (token is null) { + throw new ArgumentNullException(nameof(token)); } - } - /// - /// Provides methods allowing to manage the tokens stored in a database. - /// - /// The type of the Token entity. - /// The type of the Application entity. - /// The type of the Authorization entity. - /// The type of the Entity Framework database context. - /// The type of the entity primary keys. - public class OpenIddictEntityFrameworkCoreTokenStore : IOpenIddictTokenStore - where TToken : OpenIddictEntityFrameworkCoreToken - where TApplication : OpenIddictEntityFrameworkCoreApplication - where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization - where TContext : DbContext - where TKey : notnull, IEquatable - { - public OpenIddictEntityFrameworkCoreTokenStore( - IMemoryCache cache, - TContext context, - IOptionsMonitor options) - { - Cache = cache; - Context = context; - Options = options; - } - - /// - /// Gets the memory cache associated with the current store. - /// - protected IMemoryCache Cache { get; } - - /// - /// Gets the database context associated with the current store. - /// - protected TContext Context { get; } - - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Applications => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Authorizations => Context.Set(); - - /// - /// Gets the database set corresponding to the entity. - /// - private DbSet Tokens => Context.Set(); - - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) - => await Tokens.AsQueryable().LongCountAsync(cancellationToken); - - /// - public virtual async ValueTask CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + Context.Add(token); - return await query(Tokens).LongCountAsync(cancellationToken); - } + await Context.SaveChangesAsync(cancellationToken); + } - /// - public virtual async ValueTask CreateAsync(TToken token, CancellationToken cancellationToken) + /// + public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - Context.Add(token); + Context.Remove(token); + try + { await Context.SaveChangesAsync(cancellationToken); } - /// - public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken) + catch (DbUpdateConcurrencyException exception) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - Context.Remove(token); - - try - { - await Context.SaveChangesAsync(cancellationToken); - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(token).State = EntityState.Unchanged; - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(token).State = EntityState.Unchanged; + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247), exception); + } + } - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247), exception); - } + /// + public virtual IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync(string subject, string client, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be + // filtered using token.Application.Id.Equals(key). To work around this issue, + // this compiled query uses an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be - // filtered using token.Application.Id.Equals(key). To work around this issue, - // this compiled query uses an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Subject == subject + join application in Applications.AsTracking() on token.Application!.Id equals application.Id + where application.Id!.Equals(key) + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - where token.Subject == subject - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be + // filtered using token.Application.Id.Equals(key). To work around this issue, + // this compiled query uses an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be - // filtered using token.Application.Id.Equals(key). To work around this issue, - // this compiled query uses an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Subject == subject && + token.Status == status + join application in Applications.AsTracking() on token.Application!.Id equals application.Id + where application.Id!.Equals(key) + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - where token.Subject == subject && - token.Status == status - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be + // filtered using token.Application.Id.Equals(key). To work around this issue, + // this compiled query uses an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be - // filtered using token.Application.Id.Equals(key). To work around this issue, - // this compiled query uses an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(client); - var key = ConvertIdentifierFromString(client); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Subject == subject && + token.Status == status && + token.Type == type + join application in Applications.AsTracking() on token.Application!.Id equals application.Id + where application.Id!.Equals(key) + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - where token.Subject == subject && - token.Status == status && - token.Type == type - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be + // filtered using token.Application.Id.Equals(key). To work around this issue, + // this method is overriden to use an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be - // filtered using token.Application.Id.Equals(key). To work around this issue, - // this method is overriden to use an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(identifier); - var key = ConvertIdentifierFromString(identifier); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + join application in Applications.AsTracking() on token.Application!.Id equals application.Id + where application.Id!.Equals(key) + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - join application in Applications.AsTracking() on token.Application!.Id equals application.Id - where application.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be + // filtered using token.Authorization.Id.Equals(key). To work around this issue, + // this method is overriden to use an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. - // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be - // filtered using token.Authorization.Id.Equals(key). To work around this issue, - // this method is overriden to use an explicit join before applying the equality check. - // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + var key = ConvertIdentifierFromString(identifier); - var key = ConvertIdentifierFromString(identifier); + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + join authorization in Authorizations.AsTracking() on token.Authorization!.Id equals authorization.Id + where authorization.Id!.Equals(key) + select token).AsAsyncEnumerable(cancellationToken); + } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - join authorization in Authorizations.AsTracking() on token.Authorization!.Id equals authorization.Id - where authorization.Id!.Equals(key) - select token).AsAsyncEnumerable(cancellationToken); + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + var key = ConvertIdentifierFromString(identifier); - var key = ConvertIdentifierFromString(identifier); + return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Id!.Equals(key) + select token).FirstOrDefaultAsync(cancellationToken); + } - return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - where token.Id!.Equals(key) - select token).FirstOrDefaultAsync(cancellationToken); + /// + public virtual async ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual async ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) + return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.ReferenceId == identifier + select token).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Subject == subject + select token).AsAsyncEnumerable(cancellationToken); + } - return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - where token.ReferenceId == identifier - select token).FirstOrDefaultAsync(cancellationToken); + /// + public virtual async ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) + // If the application is not attached to the token, try to load it manually. + if (token.Application is null) { - if (string.IsNullOrEmpty(subject)) + var reference = Context.Entry(token).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + return null; } - return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() - where token.Subject == subject - select token).AsAsyncEnumerable(cancellationToken); + await reference.LoadAsync(cancellationToken); } - /// - public virtual async ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken) + if (token.Application is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return null; + } - // If the application is not attached to the token, try to load it manually. - if (token.Application is null) - { - var reference = Context.Entry(token).Reference(entry => entry.Application); - if (reference.EntityEntry.State == EntityState.Detached) - { - return null; - } + return ConvertIdentifierToString(token.Application.Id); + } - await reference.LoadAsync(cancellationToken); - } + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); + } - if (token.Application is null) - { - return null; - } + return await query(Tokens.Include(token => token.Application) + .Include(token => token.Authorization) + .AsTracking(), state).FirstOrDefaultAsync(cancellationToken); + } - return ConvertIdentifierToString(token.Application.Id); + /// + public virtual async ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + // If the authorization is not attached to the token, try to load it manually. + if (token.Authorization is null) { - if (query is null) + var reference = Context.Entry(token).Reference(entry => entry.Authorization); + if (reference.EntityEntry.State == EntityState.Detached) { - throw new ArgumentNullException(nameof(query)); + return null; } - return await query(Tokens.Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking(), state).FirstOrDefaultAsync(cancellationToken); + await reference.LoadAsync(cancellationToken); } - /// - public virtual async ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken) + if (token.Authorization is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - // If the authorization is not attached to the token, try to load it manually. - if (token.Authorization is null) - { - var reference = Context.Entry(token).Reference(entry => entry.Authorization); - if (reference.EntityEntry.State == EntityState.Detached) - { - return null; - } - - await reference.LoadAsync(cancellationToken); - } + return null; + } - if (token.Authorization is null) - { - return null; - } + return ConvertIdentifierToString(token.Authorization.Id); + } - return ConvertIdentifierToString(token.Authorization.Id); + /// + public virtual ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken) + if (token.CreationDate is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(result: null); + } - if (token.CreationDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc)); + } - return new ValueTask(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken) + if (token.ExpirationDate is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(result: null); + } - if (token.ExpirationDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc)); + } - return new ValueTask(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(ConvertIdentifierToString(token.Id)); + } - return new ValueTask(ConvertIdentifierToString(token.Id)); + /// + public virtual ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken) + return new ValueTask(token.Payload); + } + + /// + public virtual ValueTask> GetPropertiesAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - return new ValueTask(token.Payload); + if (string.IsNullOrEmpty(token.Properties)) + { + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetPropertiesAsync(TToken token, CancellationToken cancellationToken) + // Note: parsing the stringified properties is an expensive operation. + // To mitigate that, the resulting object is stored in the memory cache. + var key = string.Concat("d0509397-1bbf-40e7-97e1-5e6d7bc2536c", "\x1e", token.Properties); + var properties = Cache.GetOrCreate(key, entry => { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + entry.SetPriority(CacheItemPriority.High) + .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - if (string.IsNullOrEmpty(token.Properties)) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(token.Properties); + var builder = ImmutableDictionary.CreateBuilder(); - // Note: parsing the stringified properties is an expensive operation. - // To mitigate that, the resulting object is stored in the memory cache. - var key = string.Concat("d0509397-1bbf-40e7-97e1-5e6d7bc2536c", "\x1e", token.Properties); - var properties = Cache.GetOrCreate(key, entry => + foreach (var property in document.RootElement.EnumerateObject()) { - entry.SetPriority(CacheItemPriority.High) - .SetSlidingExpiration(TimeSpan.FromMinutes(1)); - - using var document = JsonDocument.Parse(token.Properties); - var builder = ImmutableDictionary.CreateBuilder(); + builder[property.Name] = property.Value.Clone(); + } - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return builder.ToImmutable(); + }); - return builder.ToImmutable(); - }); + return new ValueTask>(properties); + } - return new ValueTask>(properties); + /// + public virtual ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken) + if (token.RedemptionDate is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - if (token.RedemptionDate is null) - { - return new ValueTask(result: null); - } - - return new ValueTask(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc)); + return new ValueTask(result: null); } - /// - public virtual ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc)); + } - return new ValueTask(token.ReferenceId); + /// + public virtual ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(token.ReferenceId); + } - return new ValueTask(token.Status); + /// + public virtual ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(token.Status); + } - return new ValueTask(token.Subject); + /// + public virtual ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(token.Subject); + } - return new ValueTask(token.Type); + /// + public virtual ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) - { - try - { - return new ValueTask(Activator.CreateInstance()); - } + return new ValueTask(token.Type); + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0248), exception))); - } + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try + { + return new ValueTask(Activator.CreateInstance()); } - /// - public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + catch (MemberAccessException exception) { - var query = Tokens.Include(token => token.Application) - .Include(token => token.Authorization) - .OrderBy(token => token.Id!) - .AsTracking(); - - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0248), exception))); + } + } - if (count.HasValue) - { - query = query.Take(count.Value); - } + /// + public virtual IAsyncEnumerable ListAsync(int? count, int? offset, CancellationToken cancellationToken) + { + var query = Tokens.Include(token => token.Application) + .Include(token => token.Authorization) + .OrderBy(token => token.Id!) + .AsTracking(); - return query.AsAsyncEnumerable(cancellationToken); + if (offset.HasValue) + { + query = query.Skip(offset.Value); } - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + if (count.HasValue) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return query( - Tokens.Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking(), state).AsAsyncEnumerable(cancellationToken); + query = query.Take(count.Value); } - /// - public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + return query.AsAsyncEnumerable(cancellationToken); + } + + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - // Note: Entity Framework Core doesn't support set-based deletes, which prevents removing - // entities in a single command without having to retrieve and materialize them first. - // To work around this limitation, entities are manually listed and deleted using a batch logic. + throw new ArgumentNullException(nameof(query)); + } - List? exceptions = null; + return query( + Tokens.Include(token => token.Application) + .Include(token => token.Authorization) + .AsTracking(), state).AsAsyncEnumerable(cancellationToken); + } - async ValueTask CreateTransactionAsync() - { - // Note: transactions that specify an explicit isolation level are only supported by - // relational providers and trying to use them with a different provider results in - // an invalid operation exception being thrown at runtime. To prevent that, a manual - // check is made to ensure the underlying transaction manager is relational. - var manager = Context.Database.GetService(); - if (manager is IRelationalTransactionManager) - { - // Note: relational providers like Sqlite are known to lack proper support - // for repeatable read transactions. To ensure this method can be safely used - // with such providers, the database transaction is created in a try/catch block. - try - { - return await Context.Database.BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken); - } - - catch - { - return null; - } - } + /// + public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + { + // Note: Entity Framework Core doesn't support set-based deletes, which prevents removing + // entities in a single command without having to retrieve and materialize them first. + // To work around this limitation, entities are manually listed and deleted using a batch logic. - return null; - } + List? exceptions = null; - // Note: to avoid sending too many queries, the maximum number of elements - // that can be removed by a single call to PruneAsync() is deliberately limited. - for (var index = 0; index < 1_000; index++) + async ValueTask CreateTransactionAsync() + { + // Note: transactions that specify an explicit isolation level are only supported by + // relational providers and trying to use them with a different provider results in + // an invalid operation exception being thrown at runtime. To prevent that, a manual + // check is made to ensure the underlying transaction manager is relational. + var manager = Context.Database.GetService(); + if (manager is IRelationalTransactionManager) { - cancellationToken.ThrowIfCancellationRequested(); - - // To prevent concurrency exceptions from being thrown if an entry is modified - // after it was retrieved from the database, the following logic is executed in - // a repeatable read transaction, that will put a lock on the retrieved entries - // and thus prevent them from being concurrently modified outside this block. - using var transaction = await CreateTransactionAsync(); - - // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable - // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. - // To work around this limitation, the threshold represented as a DateTimeOffset - // instance is manually converted to a UTC DateTime instance outside the query. - var date = threshold.UtcDateTime; - - var tokens = await - (from token in Tokens.AsTracking() - where token.CreationDate < date - where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || - (token.Authorization != null && token.Authorization.Status != Statuses.Valid) || - token.ExpirationDate < DateTime.UtcNow - orderby token.Id - select token).Take(1_000).ToListAsync(cancellationToken); - - if (tokens.Count == 0) - { - break; - } - - Context.RemoveRange(tokens); - + // Note: relational providers like Sqlite are known to lack proper support + // for repeatable read transactions. To ensure this method can be safely used + // with such providers, the database transaction is created in a try/catch block. try { - await Context.SaveChangesAsync(cancellationToken); - transaction?.Commit(); + return await Context.Database.BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken); } - catch (Exception exception) + catch { - exceptions ??= new List(capacity: 1); - exceptions.Add(exception); + return null; } } - if (exceptions is not null) - { - throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); - } + return null; } - /// - public virtual async ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + // Note: to avoid sending too many queries, the maximum number of elements + // that can be removed by a single call to PruneAsync() is deliberately limited. + for (var index = 0; index < 1_000; index++) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + cancellationToken.ThrowIfCancellationRequested(); - if (!string.IsNullOrEmpty(identifier)) - { - var key = ConvertIdentifierFromString(identifier); + // To prevent concurrency exceptions from being thrown if an entry is modified + // after it was retrieved from the database, the following logic is executed in + // a repeatable read transaction, that will put a lock on the retrieved entries + // and thus prevent them from being concurrently modified outside this block. + using var transaction = await CreateTransactionAsync(); - // Warning: FindAsync() is deliberately not used to work around a breaking change introduced - // in Entity Framework Core 3.x (where a ValueTask instead of a Task is now returned). - var application = - await Applications.AsQueryable() - .AsTracking() - .FirstOrDefaultAsync(application => application.Id!.Equals(key), cancellationToken); + // Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable + // to create a SQL query with an expression calling DateTimeOffset.UtcDateTime. + // To work around this limitation, the threshold represented as a DateTimeOffset + // instance is manually converted to a UTC DateTime instance outside the query. + var date = threshold.UtcDateTime; - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0250)); - } + var tokens = await + (from token in Tokens.AsTracking() + where token.CreationDate < date + where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || + (token.Authorization != null && token.Authorization.Status != Statuses.Valid) || + token.ExpirationDate < DateTime.UtcNow + orderby token.Id + select token).Take(1_000).ToListAsync(cancellationToken); - token.Application = application; + if (tokens.Count == 0) + { + break; } - else - { - // If the application is not attached to the token, try to load it manually. - if (token.Application is null) - { - var reference = Context.Entry(token).Reference(entry => entry.Application); - if (reference.EntityEntry.State == EntityState.Detached) - { - return; - } + Context.RemoveRange(tokens); - await reference.LoadAsync(cancellationToken); - } + try + { + await Context.SaveChangesAsync(cancellationToken); + transaction?.Commit(); + } - token.Application = null; + catch (Exception exception) + { + exceptions ??= new List(capacity: 1); + exceptions.Add(exception); } } - /// - public virtual async ValueTask SetAuthorizationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + if (exceptions is not null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions); + } + } - if (!string.IsNullOrEmpty(identifier)) - { - var key = ConvertIdentifierFromString(identifier); + /// + public virtual async ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - // Warning: FindAsync() is deliberately not used to work around a breaking change introduced - // in Entity Framework Core 3.x (where a ValueTask instead of a Task is now returned). - var authorization = - await Authorizations.AsQueryable() - .AsTracking() - .FirstOrDefaultAsync(authorization => authorization.Id!.Equals(key), cancellationToken); + if (!string.IsNullOrEmpty(identifier)) + { + var key = ConvertIdentifierFromString(identifier); - if (authorization is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0251)); - } + // Warning: FindAsync() is deliberately not used to work around a breaking change introduced + // in Entity Framework Core 3.x (where a ValueTask instead of a Task is now returned). + var application = + await Applications.AsQueryable() + .AsTracking() + .FirstOrDefaultAsync(application => application.Id!.Equals(key), cancellationToken); - token.Authorization = authorization; + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0250)); } - else + token.Application = application; + } + + else + { + // If the application is not attached to the token, try to load it manually. + if (token.Application is null) { - // If the authorization is not attached to the token, try to load it manually. - if (token.Authorization is null) + var reference = Context.Entry(token).Reference(entry => entry.Application); + if (reference.EntityEntry.State == EntityState.Detached) { - var reference = Context.Entry(token).Reference(entry => entry.Authorization); - if (reference.EntityEntry.State == EntityState.Detached) - { - return; - } - - await reference.LoadAsync(cancellationToken); + return; } - token.Authorization = null; + await reference.LoadAsync(cancellationToken); } + + token.Application = null; } + } - /// - public virtual ValueTask SetCreationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + /// + public virtual async ValueTask SetAuthorizationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - token.CreationDate = date?.UtcDateTime; - - return default; + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask SetExpirationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + if (!string.IsNullOrEmpty(identifier)) { - if (token is null) + var key = ConvertIdentifierFromString(identifier); + + // Warning: FindAsync() is deliberately not used to work around a breaking change introduced + // in Entity Framework Core 3.x (where a ValueTask instead of a Task is now returned). + var authorization = + await Authorizations.AsQueryable() + .AsTracking() + .FirstOrDefaultAsync(authorization => authorization.Id!.Equals(key), cancellationToken); + + if (authorization is null) { - throw new ArgumentNullException(nameof(token)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0251)); } - token.ExpirationDate = date?.UtcDateTime; - - return default; + token.Authorization = authorization; } - /// - public virtual ValueTask SetPayloadAsync(TToken token, string? payload, CancellationToken cancellationToken) + else { - if (token is null) + // If the authorization is not attached to the token, try to load it manually. + if (token.Authorization is null) { - throw new ArgumentNullException(nameof(token)); - } + var reference = Context.Entry(token).Reference(entry => entry.Authorization); + if (reference.EntityEntry.State == EntityState.Detached) + { + return; + } - token.Payload = payload; + await reference.LoadAsync(cancellationToken); + } - return default; + token.Authorization = null; } + } - /// - public virtual ValueTask SetPropertiesAsync(TToken token, - ImmutableDictionary properties, CancellationToken cancellationToken) + /// + public virtual ValueTask SetCreationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - if (properties is null || properties.IsEmpty) - { - token.Properties = null; + token.CreationDate = date?.UtcDateTime; - return default; - } + return default; + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + /// + public virtual ValueTask SetExpirationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - writer.WriteStartObject(); + token.ExpirationDate = date?.UtcDateTime; - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetPayloadAsync(TToken token, string? payload, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - token.Properties = Encoding.UTF8.GetString(stream.ToArray()); + token.Payload = payload; - return default; - } + return default; + } - /// - public virtual ValueTask SetRedemptionDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + /// + public virtual ValueTask SetPropertiesAsync(TToken token, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - token.RedemptionDate = date?.UtcDateTime; + if (properties is null || properties.IsEmpty) + { + token.Properties = null; return default; } - /// - public virtual ValueTask SetReferenceIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - token.ReferenceId = identifier; + writer.WriteStartObject(); - return default; + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); } - /// - public virtual ValueTask SetStatusAsync(TToken token, string? status, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + writer.WriteEndObject(); + writer.Flush(); - token.Status = status; + token.Properties = Encoding.UTF8.GetString(stream.ToArray()); - return default; - } + return default; + } - /// - public virtual ValueTask SetSubjectAsync(TToken token, string? subject, CancellationToken cancellationToken) + /// + public virtual ValueTask SetRedemptionDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - token.Subject = subject; + token.RedemptionDate = date?.UtcDateTime; - return default; + return default; + } + + /// + public virtual ValueTask SetReferenceIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask SetTypeAsync(TToken token, string? type, CancellationToken cancellationToken) + token.ReferenceId = identifier; + + return default; + } + + /// + public virtual ValueTask SetStatusAsync(TToken token, string? status, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - token.Type = type; + token.Status = status; - return default; + return default; + } + + /// + public virtual ValueTask SetSubjectAsync(TToken token, string? subject, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken) + token.Subject = subject; + + return default; + } + + /// + public virtual ValueTask SetTypeAsync(TToken token, string? type, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } + + token.Type = type; - Context.Attach(token); + return default; + } - // Generate a new concurrency token and attach it - // to the token before persisting the changes. - token.ConcurrencyToken = Guid.NewGuid().ToString(); + /// + public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - Context.Update(token); + Context.Attach(token); - try - { - await Context.SaveChangesAsync(cancellationToken); - } + // Generate a new concurrency token and attach it + // to the token before persisting the changes. + token.ConcurrencyToken = Guid.NewGuid().ToString(); - catch (DbUpdateConcurrencyException exception) - { - // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. - Context.Entry(token).State = EntityState.Unchanged; + Context.Update(token); - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247), exception); - } + try + { + await Context.SaveChangesAsync(cancellationToken); } - /// - /// Converts the provided identifier to a strongly typed key object. - /// - /// The identifier to convert. - /// An instance of representing the provided identifier. - public virtual TKey? ConvertIdentifierFromString(string? identifier) + catch (DbUpdateConcurrencyException exception) { - if (string.IsNullOrEmpty(identifier)) - { - return default; - } + // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing. + Context.Entry(token).State = EntityState.Unchanged; - return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247), exception); } + } - /// - /// Converts the provided identifier to its string representation. - /// - /// The identifier to convert. - /// A representation of the provided identifier. - public virtual string? ConvertIdentifierToString(TKey? identifier) + /// + /// Converts the provided identifier to a strongly typed key object. + /// + /// The identifier to convert. + /// An instance of representing the provided identifier. + public virtual TKey? ConvertIdentifierFromString(string? identifier) + { + if (string.IsNullOrEmpty(identifier)) { - if (Equals(identifier, default(TKey))) - { - return null; - } + return default; + } - return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); + return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier); + } + + /// + /// Converts the provided identifier to its string representation. + /// + /// The identifier to convert. + /// A representation of the provided identifier. + public virtual string? ConvertIdentifierToString(TKey? identifier) + { + if (Equals(identifier, default(TKey))) + { + return null; } + + return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier); } -} \ No newline at end of file +} diff --git a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs index 20892af2..f288ba06 100644 --- a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs +++ b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbApplication.cs @@ -12,94 +12,93 @@ using System.Globalization; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace OpenIddict.MongoDb.Models +namespace OpenIddict.MongoDb.Models; + +/// +/// Represents an OpenIddict application. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; ClientId = {ClientId,nq} ; Type = {Type,nq}")] +public class OpenIddictMongoDbApplication { /// - /// Represents an OpenIddict application. + /// Gets or sets the client identifier associated with the current application. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; ClientId = {ClientId,nq} ; Type = {Type,nq}")] - public class OpenIddictMongoDbApplication - { - /// - /// Gets or sets the client identifier associated with the current application. - /// - [BsonElement("client_id"), BsonIgnoreIfNull] - public virtual string? ClientId { get; set; } + [BsonElement("client_id"), BsonIgnoreIfNull] + public virtual string? ClientId { get; set; } - /// - /// Gets or sets the client secret associated with the current application. - /// Note: depending on the application manager used to create this instance, - /// this property may be hashed or encrypted for security reasons. - /// - [BsonElement("client_secret"), BsonIgnoreIfNull] - public virtual string? ClientSecret { get; set; } + /// + /// Gets or sets the client secret associated with the current application. + /// Note: depending on the application manager used to create this instance, + /// this property may be hashed or encrypted for security reasons. + /// + [BsonElement("client_secret"), BsonIgnoreIfNull] + public virtual string? ClientSecret { get; set; } - /// - /// Gets or sets the concurrency token. - /// - [BsonElement("concurrency_token"), BsonIgnoreIfNull] - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + /// + /// Gets or sets the concurrency token. + /// + [BsonElement("concurrency_token"), BsonIgnoreIfNull] + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - /// - /// Gets or sets the consent type associated with the current application. - /// - [BsonElement("consent_type"), BsonIgnoreIfNull] - public virtual string? ConsentType { get; set; } + /// + /// Gets or sets the consent type associated with the current application. + /// + [BsonElement("consent_type"), BsonIgnoreIfNull] + public virtual string? ConsentType { get; set; } - /// - /// Gets or sets the display name associated with the current application. - /// - [BsonElement("display_name"), BsonIgnoreIfNull] - public virtual string? DisplayName { get; set; } + /// + /// Gets or sets the display name associated with the current application. + /// + [BsonElement("display_name"), BsonIgnoreIfNull] + public virtual string? DisplayName { get; set; } - /// - /// Gets or sets the localized display names associated with the current application. - /// - [BsonElement("display_names"), BsonIgnoreIfNull] - public virtual IReadOnlyDictionary DisplayNames { get; set; } - = ImmutableDictionary.Create(); + /// + /// Gets or sets the localized display names associated with the current application. + /// + [BsonElement("display_names"), BsonIgnoreIfNull] + public virtual IReadOnlyDictionary DisplayNames { get; set; } + = ImmutableDictionary.Create(); - /// - /// Gets or sets the unique identifier associated with the current application. - /// - [BsonId, BsonRequired] - public virtual ObjectId Id { get; set; } + /// + /// Gets or sets the unique identifier associated with the current application. + /// + [BsonId, BsonRequired] + public virtual ObjectId Id { get; set; } - /// - /// Gets or sets the permissions associated with the current application. - /// - [BsonElement("permissions"), BsonIgnoreIfDefault] - public virtual IReadOnlyList Permissions { get; set; } = ImmutableList.Create(); + /// + /// Gets or sets the permissions associated with the current application. + /// + [BsonElement("permissions"), BsonIgnoreIfDefault] + public virtual IReadOnlyList Permissions { get; set; } = ImmutableList.Create(); - /// - /// Gets or sets the logout callback URLs associated with the current application. - /// - [BsonElement("post_logout_redirect_uris"), BsonIgnoreIfDefault] - public virtual IReadOnlyList PostLogoutRedirectUris { get; set; } = ImmutableList.Create(); + /// + /// Gets or sets the logout callback URLs associated with the current application. + /// + [BsonElement("post_logout_redirect_uris"), BsonIgnoreIfDefault] + public virtual IReadOnlyList PostLogoutRedirectUris { get; set; } = ImmutableList.Create(); - /// - /// Gets or sets the additional properties associated with the current application. - /// - [BsonElement("properties"), BsonIgnoreIfNull] - public virtual BsonDocument? Properties { get; set; } + /// + /// Gets or sets the additional properties associated with the current application. + /// + [BsonElement("properties"), BsonIgnoreIfNull] + public virtual BsonDocument? Properties { get; set; } - /// - /// Gets or sets the callback URLs associated with the current application. - /// - [BsonElement("redirect_uris"), BsonIgnoreIfDefault] - public virtual IReadOnlyList RedirectUris { get; set; } = ImmutableList.Create(); + /// + /// Gets or sets the callback URLs associated with the current application. + /// + [BsonElement("redirect_uris"), BsonIgnoreIfDefault] + public virtual IReadOnlyList RedirectUris { get; set; } = ImmutableList.Create(); - /// - /// Gets or sets the requirements associated with the current application. - /// - [BsonElement("requirements"), BsonIgnoreIfDefault] - public virtual IReadOnlyList Requirements { get; set; } = ImmutableList.Create(); + /// + /// Gets or sets the requirements associated with the current application. + /// + [BsonElement("requirements"), BsonIgnoreIfDefault] + public virtual IReadOnlyList Requirements { get; set; } = ImmutableList.Create(); - /// - /// Gets or sets the application type - /// associated with the current application. - /// - [BsonElement("type"), BsonIgnoreIfNull] - public virtual string? Type { get; set; } - } -} \ No newline at end of file + /// + /// Gets or sets the application type + /// associated with the current application. + /// + [BsonElement("type"), BsonIgnoreIfNull] + public virtual string? Type { get; set; } +} diff --git a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs index 64b51b5f..f403f27e 100644 --- a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs +++ b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbAuthorization.cs @@ -11,65 +11,64 @@ using System.Diagnostics; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace OpenIddict.MongoDb.Models +namespace OpenIddict.MongoDb.Models; + +/// +/// Represents an OpenIddict authorization. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] +public class OpenIddictMongoDbAuthorization { /// - /// Represents an OpenIddict authorization. + /// Gets or sets the identifier of the application associated with the current authorization. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] - public class OpenIddictMongoDbAuthorization - { - /// - /// Gets or sets the identifier of the application associated with the current authorization. - /// - [BsonElement("application_id"), BsonIgnoreIfDefault] - public virtual ObjectId ApplicationId { get; set; } + [BsonElement("application_id"), BsonIgnoreIfDefault] + public virtual ObjectId ApplicationId { get; set; } - /// - /// Gets or sets the concurrency token. - /// - [BsonElement("concurrency_token"), BsonIgnoreIfNull] - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + /// + /// Gets or sets the concurrency token. + /// + [BsonElement("concurrency_token"), BsonIgnoreIfNull] + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - /// - /// Gets or sets the UTC creation date of the current authorization. - /// - public virtual DateTime? CreationDate { get; set; } + /// + /// Gets or sets the UTC creation date of the current authorization. + /// + public virtual DateTime? CreationDate { get; set; } - /// - /// Gets or sets the unique identifier associated with the current authorization. - /// - [BsonId, BsonRequired] - public virtual ObjectId Id { get; set; } + /// + /// Gets or sets the unique identifier associated with the current authorization. + /// + [BsonId, BsonRequired] + public virtual ObjectId Id { get; set; } - /// - /// Gets or sets the additional properties associated with the current authorization. - /// - [BsonElement("properties"), BsonIgnoreIfNull] - public virtual BsonDocument? Properties { get; set; } + /// + /// Gets or sets the additional properties associated with the current authorization. + /// + [BsonElement("properties"), BsonIgnoreIfNull] + public virtual BsonDocument? Properties { get; set; } - /// - /// Gets or sets the scopes associated with the current authorization. - /// - [BsonElement("scopes"), BsonIgnoreIfDefault] - public virtual IReadOnlyList Scopes { get; set; } = ImmutableList.Create(); + /// + /// Gets or sets the scopes associated with the current authorization. + /// + [BsonElement("scopes"), BsonIgnoreIfDefault] + public virtual IReadOnlyList Scopes { get; set; } = ImmutableList.Create(); - /// - /// Gets or sets the status of the current authorization. - /// - [BsonElement("status"), BsonIgnoreIfNull] - public virtual string? Status { get; set; } + /// + /// Gets or sets the status of the current authorization. + /// + [BsonElement("status"), BsonIgnoreIfNull] + public virtual string? Status { get; set; } - /// - /// Gets or sets the subject associated with the current authorization. - /// - [BsonElement("subject"), BsonIgnoreIfNull] - public virtual string? Subject { get; set; } + /// + /// Gets or sets the subject associated with the current authorization. + /// + [BsonElement("subject"), BsonIgnoreIfNull] + public virtual string? Subject { get; set; } - /// - /// Gets or sets the type of the current authorization. - /// - [BsonElement("type"), BsonIgnoreIfNull] - public virtual string? Type { get; set; } - } + /// + /// Gets or sets the type of the current authorization. + /// + [BsonElement("type"), BsonIgnoreIfNull] + public virtual string? Type { get; set; } } diff --git a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs index 67521333..5e121d0f 100644 --- a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs +++ b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbScope.cs @@ -12,68 +12,67 @@ using System.Globalization; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace OpenIddict.MongoDb.Models +namespace OpenIddict.MongoDb.Models; + +/// +/// Represents an OpenIddict scope. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; Name = {Name,nq}")] +public class OpenIddictMongoDbScope { /// - /// Represents an OpenIddict scope. + /// Gets or sets the concurrency token. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; Name = {Name,nq}")] - public class OpenIddictMongoDbScope - { - /// - /// Gets or sets the concurrency token. - /// - [BsonElement("concurrency_token"), BsonIgnoreIfNull] - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + [BsonElement("concurrency_token"), BsonIgnoreIfNull] + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - /// - /// Gets or sets the public description associated with the current scope. - /// - [BsonElement("description"), BsonIgnoreIfNull] - public virtual string? Description { get; set; } + /// + /// Gets or sets the public description associated with the current scope. + /// + [BsonElement("description"), BsonIgnoreIfNull] + public virtual string? Description { get; set; } - /// - /// Gets or sets the localized public descriptions associated with the current scope. - /// - [BsonElement("descriptions"), BsonIgnoreIfNull] - public virtual IReadOnlyDictionary Descriptions { get; set; } - = ImmutableDictionary.Create(); + /// + /// Gets or sets the localized public descriptions associated with the current scope. + /// + [BsonElement("descriptions"), BsonIgnoreIfNull] + public virtual IReadOnlyDictionary Descriptions { get; set; } + = ImmutableDictionary.Create(); - /// - /// Gets or sets the display name associated with the current scope. - /// - [BsonElement("display_name"), BsonIgnoreIfNull] - public virtual string? DisplayName { get; set; } + /// + /// Gets or sets the display name associated with the current scope. + /// + [BsonElement("display_name"), BsonIgnoreIfNull] + public virtual string? DisplayName { get; set; } - /// - /// Gets or sets the localized display names associated with the current scope. - /// - [BsonElement("display_names"), BsonIgnoreIfNull] - public virtual IReadOnlyDictionary DisplayNames { get; set; } - = ImmutableDictionary.Create(); + /// + /// Gets or sets the localized display names associated with the current scope. + /// + [BsonElement("display_names"), BsonIgnoreIfNull] + public virtual IReadOnlyDictionary DisplayNames { get; set; } + = ImmutableDictionary.Create(); - /// - /// Gets or sets the unique identifier associated with the current scope. - /// - [BsonId, BsonRequired] - public virtual ObjectId Id { get; set; } + /// + /// Gets or sets the unique identifier associated with the current scope. + /// + [BsonId, BsonRequired] + public virtual ObjectId Id { get; set; } - /// - /// Gets or sets the unique name associated with the current scope. - /// - [BsonElement("name"), BsonIgnoreIfNull] - public virtual string? Name { get; set; } + /// + /// Gets or sets the unique name associated with the current scope. + /// + [BsonElement("name"), BsonIgnoreIfNull] + public virtual string? Name { get; set; } - /// - /// Gets or sets the additional properties associated with the current scope. - /// - [BsonElement("properties"), BsonIgnoreIfNull] - public virtual BsonDocument? Properties { get; set; } + /// + /// Gets or sets the additional properties associated with the current scope. + /// + [BsonElement("properties"), BsonIgnoreIfNull] + public virtual BsonDocument? Properties { get; set; } - /// - /// Gets or sets the resources associated with the current scope. - /// - [BsonElement("resources"), BsonIgnoreIfDefault] - public virtual IReadOnlyList Resources { get; set; } = ImmutableList.Create(); - } + /// + /// Gets or sets the resources associated with the current scope. + /// + [BsonElement("resources"), BsonIgnoreIfDefault] + public virtual IReadOnlyList Resources { get; set; } = ImmutableList.Create(); } diff --git a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs index 89a53e85..9bbb45a4 100644 --- a/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs +++ b/src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs @@ -9,95 +9,94 @@ using System.Diagnostics; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace OpenIddict.MongoDb.Models +namespace OpenIddict.MongoDb.Models; + +/// +/// Represents an OpenIddict token. +/// +[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] +public class OpenIddictMongoDbToken { /// - /// Represents an OpenIddict token. + /// Gets or sets the identifier of the application associated with the current token. /// - [DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")] - public class OpenIddictMongoDbToken - { - /// - /// Gets or sets the identifier of the application associated with the current token. - /// - [BsonElement("application_id"), BsonIgnoreIfDefault] - public virtual ObjectId ApplicationId { get; set; } + [BsonElement("application_id"), BsonIgnoreIfDefault] + public virtual ObjectId ApplicationId { get; set; } - /// - /// Gets or sets the identifier of the authorization associated with the current token. - /// - [BsonElement("authorization_id"), BsonIgnoreIfDefault] - public virtual ObjectId AuthorizationId { get; set; } + /// + /// Gets or sets the identifier of the authorization associated with the current token. + /// + [BsonElement("authorization_id"), BsonIgnoreIfDefault] + public virtual ObjectId AuthorizationId { get; set; } - /// - /// Gets or sets the concurrency token. - /// - [BsonElement("concurrency_token"), BsonIgnoreIfNull] - public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + /// + /// Gets or sets the concurrency token. + /// + [BsonElement("concurrency_token"), BsonIgnoreIfNull] + public virtual string? ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); - /// - /// Gets or sets the UTC creation date of the current token. - /// - [BsonElement("creation_date"), BsonIgnoreIfNull] - public virtual DateTime? CreationDate { get; set; } + /// + /// Gets or sets the UTC creation date of the current token. + /// + [BsonElement("creation_date"), BsonIgnoreIfNull] + public virtual DateTime? CreationDate { get; set; } - /// - /// Gets or sets the UTC expiration date of the current token. - /// - [BsonElement("expiration_date"), BsonIgnoreIfNull] - public virtual DateTime? ExpirationDate { get; set; } + /// + /// Gets or sets the UTC expiration date of the current token. + /// + [BsonElement("expiration_date"), BsonIgnoreIfNull] + public virtual DateTime? ExpirationDate { get; set; } - /// - /// Gets or sets the unique identifier associated with the current token. - /// - [BsonId, BsonRequired] - public virtual ObjectId Id { get; set; } + /// + /// Gets or sets the unique identifier associated with the current token. + /// + [BsonId, BsonRequired] + public virtual ObjectId Id { get; set; } - /// - /// Gets or sets the payload of the current token, if applicable. - /// Note: this property is only used for reference tokens - /// and may be encrypted for security reasons. - /// - [BsonElement("payload"), BsonIgnoreIfNull] - public virtual string? Payload { get; set; } + /// + /// Gets or sets the payload of the current token, if applicable. + /// Note: this property is only used for reference tokens + /// and may be encrypted for security reasons. + /// + [BsonElement("payload"), BsonIgnoreIfNull] + public virtual string? Payload { get; set; } - /// - /// Gets or sets the additional properties associated with the current token. - /// - [BsonElement("properties"), BsonIgnoreIfNull] - public virtual BsonDocument? Properties { get; set; } + /// + /// Gets or sets the additional properties associated with the current token. + /// + [BsonElement("properties"), BsonIgnoreIfNull] + public virtual BsonDocument? Properties { get; set; } - /// - /// Gets or sets the UTC redemption date of the current token. - /// - [BsonElement("redemption_date"), BsonIgnoreIfNull] - public virtual DateTime? RedemptionDate { get; set; } + /// + /// Gets or sets the UTC redemption date of the current token. + /// + [BsonElement("redemption_date"), BsonIgnoreIfNull] + public virtual DateTime? RedemptionDate { get; set; } - /// - /// Gets or sets the reference identifier associated - /// with the current token, if applicable. - /// Note: this property is only used for reference tokens - /// and may be hashed or encrypted for security reasons. - /// - [BsonElement("reference_id"), BsonIgnoreIfNull] - public virtual string? ReferenceId { get; set; } + /// + /// Gets or sets the reference identifier associated + /// with the current token, if applicable. + /// Note: this property is only used for reference tokens + /// and may be hashed or encrypted for security reasons. + /// + [BsonElement("reference_id"), BsonIgnoreIfNull] + public virtual string? ReferenceId { get; set; } - /// - /// Gets or sets the status of the current token. - /// - [BsonElement("status"), BsonIgnoreIfNull] - public virtual string? Status { get; set; } + /// + /// Gets or sets the status of the current token. + /// + [BsonElement("status"), BsonIgnoreIfNull] + public virtual string? Status { get; set; } - /// - /// Gets or sets the subject associated with the current token. - /// - [BsonElement("subject"), BsonIgnoreIfDefault] - public virtual string? Subject { get; set; } + /// + /// Gets or sets the subject associated with the current token. + /// + [BsonElement("subject"), BsonIgnoreIfDefault] + public virtual string? Subject { get; set; } - /// - /// Gets or sets the type of the current token. - /// - [BsonElement("type"), BsonIgnoreIfNull] - public virtual string? Type { get; set; } - } + /// + /// Gets or sets the type of the current token. + /// + [BsonElement("type"), BsonIgnoreIfNull] + public virtual string? Type { get; set; } } diff --git a/src/OpenIddict.MongoDb/IOpenIddictMongoDbContext.cs b/src/OpenIddict.MongoDb/IOpenIddictMongoDbContext.cs index f2bd207f..81d2a31d 100644 --- a/src/OpenIddict.MongoDb/IOpenIddictMongoDbContext.cs +++ b/src/OpenIddict.MongoDb/IOpenIddictMongoDbContext.cs @@ -8,20 +8,19 @@ using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Exposes the MongoDB database used by the OpenIddict stores. +/// +public interface IOpenIddictMongoDbContext { /// - /// Exposes the MongoDB database used by the OpenIddict stores. + /// Gets the . /// - public interface IOpenIddictMongoDbContext - { - /// - /// Gets the . - /// - /// - /// A that can be used to monitor the - /// asynchronous operation, whose result returns the MongoDB database. - /// - ValueTask GetDatabaseAsync(CancellationToken cancellationToken); - } -} \ No newline at end of file + /// + /// A that can be used to monitor the + /// asynchronous operation, whose result returns the MongoDB database. + /// + ValueTask GetDatabaseAsync(CancellationToken cancellationToken); +} diff --git a/src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs b/src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs index b68ec59f..3e886fd7 100644 --- a/src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs +++ b/src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs @@ -12,178 +12,177 @@ using OpenIddict.MongoDb; using OpenIddict.MongoDb.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the OpenIddict MongoDB services. +/// +public class OpenIddictMongoDbBuilder { /// - /// Exposes the necessary methods required to configure the OpenIddict MongoDB services. + /// Initializes a new instance of . /// - public class OpenIddictMongoDbBuilder + /// The services collection. + public OpenIddictMongoDbBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict MongoDB configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictMongoDbBuilder Configure(Action configuration) { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictMongoDbBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Amends the default OpenIddict MongoDB configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictMongoDbBuilder Configure(Action configuration) + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Configures OpenIddict to use the specified entity as the default application entity. - /// - /// The . - public OpenIddictMongoDbBuilder ReplaceDefaultApplicationEntity() - where TApplication : OpenIddictMongoDbApplication - { - Services.Configure(options => options.DefaultApplicationType = typeof(TApplication)); + /// + /// Configures OpenIddict to use the specified entity as the default application entity. + /// + /// The . + public OpenIddictMongoDbBuilder ReplaceDefaultApplicationEntity() + where TApplication : OpenIddictMongoDbApplication + { + Services.Configure(options => options.DefaultApplicationType = typeof(TApplication)); - return this; - } + return this; + } - /// - /// Configures OpenIddict to use the specified entity as the default authorization entity. - /// - /// The . - public OpenIddictMongoDbBuilder ReplaceDefaultAuthorizationEntity() - where TAuthorization : OpenIddictMongoDbAuthorization - { - Services.Configure(options => options.DefaultAuthorizationType = typeof(TAuthorization)); + /// + /// Configures OpenIddict to use the specified entity as the default authorization entity. + /// + /// The . + public OpenIddictMongoDbBuilder ReplaceDefaultAuthorizationEntity() + where TAuthorization : OpenIddictMongoDbAuthorization + { + Services.Configure(options => options.DefaultAuthorizationType = typeof(TAuthorization)); - return this; - } + return this; + } - /// - /// Configures OpenIddict to use the specified entity as the default scope entity. - /// - /// The . - public OpenIddictMongoDbBuilder ReplaceDefaultScopeEntity() - where TScope : OpenIddictMongoDbScope - { - Services.Configure(options => options.DefaultScopeType = typeof(TScope)); + /// + /// Configures OpenIddict to use the specified entity as the default scope entity. + /// + /// The . + public OpenIddictMongoDbBuilder ReplaceDefaultScopeEntity() + where TScope : OpenIddictMongoDbScope + { + Services.Configure(options => options.DefaultScopeType = typeof(TScope)); - return this; - } + return this; + } - /// - /// Configures OpenIddict to use the specified entity as the default token entity. - /// - /// The . - public OpenIddictMongoDbBuilder ReplaceDefaultTokenEntity() - where TToken : OpenIddictMongoDbToken - { - Services.Configure(options => options.DefaultTokenType = typeof(TToken)); + /// + /// Configures OpenIddict to use the specified entity as the default token entity. + /// + /// The . + public OpenIddictMongoDbBuilder ReplaceDefaultTokenEntity() + where TToken : OpenIddictMongoDbToken + { + Services.Configure(options => options.DefaultTokenType = typeof(TToken)); - return this; - } + return this; + } - /// - /// Replaces the default applications collection name (by default, openiddict.applications). - /// - /// The collection name - /// The . - public OpenIddictMongoDbBuilder SetApplicationsCollectionName(string name) + /// + /// Replaces the default applications collection name (by default, openiddict.applications). + /// + /// The collection name + /// The . + public OpenIddictMongoDbBuilder SetApplicationsCollectionName(string name) + { + if (string.IsNullOrEmpty(name)) { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0261), nameof(name)); - } - - return Configure(options => options.ApplicationsCollectionName = name); + throw new ArgumentException(SR.GetResourceString(SR.ID0261), nameof(name)); } - /// - /// Replaces the default authorizations collection name (by default, openiddict.authorizations). - /// - /// The collection name - /// The . - public OpenIddictMongoDbBuilder SetAuthorizationsCollectionName(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0261), nameof(name)); - } + return Configure(options => options.ApplicationsCollectionName = name); + } - return Configure(options => options.AuthorizationsCollectionName = name); + /// + /// Replaces the default authorizations collection name (by default, openiddict.authorizations). + /// + /// The collection name + /// The . + public OpenIddictMongoDbBuilder SetAuthorizationsCollectionName(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0261), nameof(name)); } - /// - /// Replaces the default scopes collection name (by default, openiddict.scopes). - /// - /// The collection name - /// The . - public OpenIddictMongoDbBuilder SetScopesCollectionName(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0261), nameof(name)); - } + return Configure(options => options.AuthorizationsCollectionName = name); + } - return Configure(options => options.ScopesCollectionName = name); + /// + /// Replaces the default scopes collection name (by default, openiddict.scopes). + /// + /// The collection name + /// The . + public OpenIddictMongoDbBuilder SetScopesCollectionName(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0261), nameof(name)); } - /// - /// Replaces the default tokens collection name (by default, openiddict.tokens). - /// - /// The collection name - /// The . - public OpenIddictMongoDbBuilder SetTokensCollectionName(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0261), nameof(name)); - } + return Configure(options => options.ScopesCollectionName = name); + } - return Configure(options => options.TokensCollectionName = name); + /// + /// Replaces the default tokens collection name (by default, openiddict.tokens). + /// + /// The collection name + /// The . + public OpenIddictMongoDbBuilder SetTokensCollectionName(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0261), nameof(name)); } - /// - /// Configures the MongoDB stores to use the specified database - /// instead of retrieving it from the dependency injection container. - /// - /// The . - /// The . - public OpenIddictMongoDbBuilder UseDatabase(IMongoDatabase database) - { - if (database is null) - { - throw new ArgumentNullException(nameof(database)); - } + return Configure(options => options.TokensCollectionName = name); + } - return Configure(options => options.Database = database); + /// + /// Configures the MongoDB stores to use the specified database + /// instead of retrieving it from the dependency injection container. + /// + /// The . + /// The . + public OpenIddictMongoDbBuilder UseDatabase(IMongoDatabase database) + { + if (database is null) + { + throw new ArgumentNullException(nameof(database)); } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + return Configure(options => options.Database = database); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); - } + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs b/src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs index 82c5e4ec..c7e2c6d4 100644 --- a/src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs +++ b/src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs @@ -12,43 +12,42 @@ using Microsoft.Extensions.Options; using MongoDB.Driver; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +public class OpenIddictMongoDbContext : IOpenIddictMongoDbContext { + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + public OpenIddictMongoDbContext( + IOptionsMonitor options, + IServiceProvider provider) + { + _options = options; + _provider = provider; + } + /// - public class OpenIddictMongoDbContext : IOpenIddictMongoDbContext + public ValueTask GetDatabaseAsync(CancellationToken cancellationToken) { - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; + if (cancellationToken.IsCancellationRequested) + { + return new ValueTask(Task.FromCanceled(cancellationToken)); + } - public OpenIddictMongoDbContext( - IOptionsMonitor options, - IServiceProvider provider) + var database = _options.CurrentValue.Database; + if (database is null) { - _options = options; - _provider = provider; + database = _provider.GetService(); } - /// - public ValueTask GetDatabaseAsync(CancellationToken cancellationToken) + if (database is null) { - if (cancellationToken.IsCancellationRequested) - { - return new ValueTask(Task.FromCanceled(cancellationToken)); - } - - var database = _options.CurrentValue.Database; - if (database is null) - { - database = _provider.GetService(); - } - - if (database is null) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0262)))); - } - - return new ValueTask(database); + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0262)))); } + + return new ValueTask(database); } } diff --git a/src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs b/src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs index c6bd8ced..3609501c 100644 --- a/src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs +++ b/src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs @@ -9,77 +9,76 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using OpenIddict.MongoDb; using OpenIddict.MongoDb.Models; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict MongoDB services. +/// +public static class OpenIddictMongoDbExtensions { /// - /// Exposes extensions allowing to register the OpenIddict MongoDB services. + /// Registers the MongoDB stores services in the DI container and + /// configures OpenIddict to use the MongoDB entities by default. /// - public static class OpenIddictMongoDbExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictMongoDbBuilder UseMongoDb(this OpenIddictCoreBuilder builder) { - /// - /// Registers the MongoDB stores services in the DI container and - /// configures OpenIddict to use the MongoDB entities by default. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictMongoDbBuilder UseMongoDb(this OpenIddictCoreBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - // Note: Mongo uses simple binary comparison checks by default so the additional - // query filtering applied by the default OpenIddict managers can be safely disabled. - builder.DisableAdditionalFiltering(); + // Note: Mongo uses simple binary comparison checks by default so the additional + // query filtering applied by the default OpenIddict managers can be safely disabled. + builder.DisableAdditionalFiltering(); - builder.SetDefaultApplicationEntity() - .SetDefaultAuthorizationEntity() - .SetDefaultScopeEntity() - .SetDefaultTokenEntity(); + builder.SetDefaultApplicationEntity() + .SetDefaultAuthorizationEntity() + .SetDefaultScopeEntity() + .SetDefaultTokenEntity(); - // Note: the Mongo stores/resolvers don't depend on scoped/transient services and thus - // can be safely registered as singleton services and shared/reused across requests. - builder.ReplaceApplicationStoreResolver(ServiceLifetime.Singleton) - .ReplaceAuthorizationStoreResolver(ServiceLifetime.Singleton) - .ReplaceScopeStoreResolver(ServiceLifetime.Singleton) - .ReplaceTokenStoreResolver(ServiceLifetime.Singleton); + // Note: the Mongo stores/resolvers don't depend on scoped/transient services and thus + // can be safely registered as singleton services and shared/reused across requests. + builder.ReplaceApplicationStoreResolver(ServiceLifetime.Singleton) + .ReplaceAuthorizationStoreResolver(ServiceLifetime.Singleton) + .ReplaceScopeStoreResolver(ServiceLifetime.Singleton) + .ReplaceTokenStoreResolver(ServiceLifetime.Singleton); - builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbApplicationStore<>)); - builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbAuthorizationStore<>)); - builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbScopeStore<>)); - builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbTokenStore<>)); + builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbApplicationStore<>)); + builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbAuthorizationStore<>)); + builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbScopeStore<>)); + builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbTokenStore<>)); - builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); - return new OpenIddictMongoDbBuilder(builder.Services); - } + return new OpenIddictMongoDbBuilder(builder.Services); + } - /// - /// Registers the MongoDB stores services in the DI container and - /// configures OpenIddict to use the MongoDB entities by default. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the MongoDB services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictCoreBuilder UseMongoDb( - this OpenIddictCoreBuilder builder, Action configuration) + /// + /// Registers the MongoDB stores services in the DI container and + /// configures OpenIddict to use the MongoDB entities by default. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the MongoDB services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictCoreBuilder UseMongoDb( + this OpenIddictCoreBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseMongoDb()); + configuration(builder.UseMongoDb()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.MongoDb/OpenIddictMongoDbHelpers.cs b/src/OpenIddict.MongoDb/OpenIddictMongoDbHelpers.cs index eff3ff29..b3649b1e 100644 --- a/src/OpenIddict.MongoDb/OpenIddictMongoDbHelpers.cs +++ b/src/OpenIddict.MongoDb/OpenIddictMongoDbHelpers.cs @@ -10,58 +10,57 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading; -namespace MongoDB.Driver +namespace MongoDB.Driver; + +/// +/// Exposes extensions simplifying the integration between OpenIddict and MongoDB. +/// +internal static class OpenIddictMongoDbHelpers { /// - /// Exposes extensions simplifying the integration between OpenIddict and MongoDB. + /// Executes the query and returns the results as a streamed async enumeration. /// - internal static class OpenIddictMongoDbHelpers + /// The type of the returned entities. + /// The query source. + /// The that can be used to abort the operation. + /// The streamed async enumeration containing the results. + internal static IAsyncEnumerable ToAsyncEnumerable(this IAsyncCursorSource source, CancellationToken cancellationToken) { - /// - /// Executes the query and returns the results as a streamed async enumeration. - /// - /// The type of the returned entities. - /// The query source. - /// The that can be used to abort the operation. - /// The streamed async enumeration containing the results. - internal static IAsyncEnumerable ToAsyncEnumerable(this IAsyncCursorSource source, CancellationToken cancellationToken) + if (source is null) { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } + throw new ArgumentNullException(nameof(source)); + } - return ExecuteAsync(source, cancellationToken); + return ExecuteAsync(source, cancellationToken); - static async IAsyncEnumerable ExecuteAsync(IAsyncCursorSource source, [EnumeratorCancellation] CancellationToken cancellationToken) - { - using var cursor = await source.ToCursorAsync(cancellationToken); + static async IAsyncEnumerable ExecuteAsync(IAsyncCursorSource source, [EnumeratorCancellation] CancellationToken cancellationToken) + { + using var cursor = await source.ToCursorAsync(cancellationToken); - while (await cursor.MoveNextAsync(cancellationToken)) + while (await cursor.MoveNextAsync(cancellationToken)) + { + foreach (var element in cursor.Current) { - foreach (var element in cursor.Current) - { - yield return element; - } + yield return element; } } } + } - /// - /// Executes the query and returns the results as a streamed async enumeration. - /// - /// The type of the returned entities. - /// The query source. - /// The that can be used to abort the operation. - /// The streamed async enumeration containing the results. - internal static IAsyncEnumerable ToAsyncEnumerable(this IQueryable source, CancellationToken cancellationToken) + /// + /// Executes the query and returns the results as a streamed async enumeration. + /// + /// The type of the returned entities. + /// The query source. + /// The that can be used to abort the operation. + /// The streamed async enumeration containing the results. + internal static IAsyncEnumerable ToAsyncEnumerable(this IQueryable source, CancellationToken cancellationToken) + { + if (source is null) { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } - - return ((IAsyncCursorSource) source).ToAsyncEnumerable(cancellationToken); + throw new ArgumentNullException(nameof(source)); } + + return ((IAsyncCursorSource) source).ToAsyncEnumerable(cancellationToken); } } diff --git a/src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs b/src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs index 8857fd58..3ea28817 100644 --- a/src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs +++ b/src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs @@ -6,37 +6,36 @@ using MongoDB.Driver; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Provides various settings needed to configure the OpenIddict MongoDB integration. +/// +public class OpenIddictMongoDbOptions { /// - /// Provides various settings needed to configure the OpenIddict MongoDB integration. + /// Gets or sets the name of the applications collection (by default, openiddict.applications). /// - public class OpenIddictMongoDbOptions - { - /// - /// Gets or sets the name of the applications collection (by default, openiddict.applications). - /// - public string ApplicationsCollectionName { get; set; } = "openiddict.applications"; + public string ApplicationsCollectionName { get; set; } = "openiddict.applications"; - /// - /// Gets or sets the name of the authorizations collection (by default, openiddict.authorizations). - /// - public string AuthorizationsCollectionName { get; set; } = "openiddict.authorizations"; + /// + /// Gets or sets the name of the authorizations collection (by default, openiddict.authorizations). + /// + public string AuthorizationsCollectionName { get; set; } = "openiddict.authorizations"; - /// - /// Gets or sets the used by the OpenIddict stores. - /// If no value is explicitly set, the database is resolved from the DI container. - /// - public IMongoDatabase? Database { get; set; } + /// + /// Gets or sets the used by the OpenIddict stores. + /// If no value is explicitly set, the database is resolved from the DI container. + /// + public IMongoDatabase? Database { get; set; } - /// - /// Gets or sets the name of the scopes collection (by default, openiddict.scopes). - /// - public string ScopesCollectionName { get; set; } = "openiddict.scopes"; + /// + /// Gets or sets the name of the scopes collection (by default, openiddict.scopes). + /// + public string ScopesCollectionName { get; set; } = "openiddict.scopes"; - /// - /// Gets or sets the name of the tokens collection (by default, openiddict.tokens). - /// - public string TokensCollectionName { get; set; } = "openiddict.tokens"; - } + /// + /// Gets or sets the name of the tokens collection (by default, openiddict.tokens). + /// + public string TokensCollectionName { get; set; } = "openiddict.tokens"; } diff --git a/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbApplicationStoreResolver.cs b/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbApplicationStoreResolver.cs index 35c60ab7..70b20024 100644 --- a/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbApplicationStoreResolver.cs +++ b/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbApplicationStoreResolver.cs @@ -11,44 +11,43 @@ using OpenIddict.Abstractions; using OpenIddict.MongoDb.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Exposes a method allowing to resolve an application store. +/// +public class OpenIddictMongoDbApplicationStoreResolver : IOpenIddictApplicationStoreResolver { + private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictMongoDbApplicationStoreResolver(IServiceProvider provider) + => _provider = provider; + /// - /// Exposes a method allowing to resolve an application store. + /// Returns an application store compatible with the specified application type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictMongoDbApplicationStoreResolver : IOpenIddictApplicationStoreResolver + /// The type of the Application entity. + /// An . + public IOpenIddictApplicationStore Get() where TApplication : class { - private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - private readonly IServiceProvider _provider; - - public OpenIddictMongoDbApplicationStoreResolver(IServiceProvider provider) - => _provider = provider; - - /// - /// Returns an application store compatible with the specified application type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Application entity. - /// An . - public IOpenIddictApplicationStore Get() where TApplication : class + var store = _provider.GetService>(); + if (store is not null) { - var store = _provider.GetService>(); - if (store is not null) - { - return store; - } + return store; + } - var type = _cache.GetOrAdd(typeof(TApplication), key => + var type = _cache.GetOrAdd(typeof(TApplication), key => + { + if (!typeof(OpenIddictMongoDbApplication).IsAssignableFrom(key)) { - if (!typeof(OpenIddictMongoDbApplication).IsAssignableFrom(key)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0257)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0257)); + } - return typeof(OpenIddictMongoDbApplicationStore<>).MakeGenericType(key); - }); + return typeof(OpenIddictMongoDbApplicationStore<>).MakeGenericType(key); + }); - return (IOpenIddictApplicationStore) _provider.GetRequiredService(type); - } + return (IOpenIddictApplicationStore) _provider.GetRequiredService(type); } } diff --git a/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbAuthorizationStoreResolver.cs b/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbAuthorizationStoreResolver.cs index 45039b31..72b117de 100644 --- a/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbAuthorizationStoreResolver.cs +++ b/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbAuthorizationStoreResolver.cs @@ -11,44 +11,43 @@ using OpenIddict.Abstractions; using OpenIddict.MongoDb.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Exposes a method allowing to resolve an authorization store. +/// +public class OpenIddictMongoDbAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver { + private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictMongoDbAuthorizationStoreResolver(IServiceProvider provider) + => _provider = provider; + /// - /// Exposes a method allowing to resolve an authorization store. + /// Returns an authorization store compatible with the specified authorization type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictMongoDbAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver + /// The type of the Authorization entity. + /// An . + public IOpenIddictAuthorizationStore Get() where TAuthorization : class { - private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - private readonly IServiceProvider _provider; - - public OpenIddictMongoDbAuthorizationStoreResolver(IServiceProvider provider) - => _provider = provider; - - /// - /// Returns an authorization store compatible with the specified authorization type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Authorization entity. - /// An . - public IOpenIddictAuthorizationStore Get() where TAuthorization : class + var store = _provider.GetService>(); + if (store is not null) { - var store = _provider.GetService>(); - if (store is not null) - { - return store; - } + return store; + } - var type = _cache.GetOrAdd(typeof(TAuthorization), key => + var type = _cache.GetOrAdd(typeof(TAuthorization), key => + { + if (!typeof(OpenIddictMongoDbAuthorization).IsAssignableFrom(key)) { - if (!typeof(OpenIddictMongoDbAuthorization).IsAssignableFrom(key)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0258)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0258)); + } - return typeof(OpenIddictMongoDbAuthorizationStore<>).MakeGenericType(key); - }); + return typeof(OpenIddictMongoDbAuthorizationStore<>).MakeGenericType(key); + }); - return (IOpenIddictAuthorizationStore) _provider.GetRequiredService(type); - } + return (IOpenIddictAuthorizationStore) _provider.GetRequiredService(type); } } diff --git a/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbScopeStoreResolver.cs b/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbScopeStoreResolver.cs index f1336e47..5471d0a7 100644 --- a/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbScopeStoreResolver.cs +++ b/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbScopeStoreResolver.cs @@ -11,44 +11,43 @@ using OpenIddict.Abstractions; using OpenIddict.MongoDb.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Exposes a method allowing to resolve a scope store. +/// +public class OpenIddictMongoDbScopeStoreResolver : IOpenIddictScopeStoreResolver { + private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictMongoDbScopeStoreResolver(IServiceProvider provider) + => _provider = provider; + /// - /// Exposes a method allowing to resolve a scope store. + /// Returns a scope store compatible with the specified scope type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictMongoDbScopeStoreResolver : IOpenIddictScopeStoreResolver + /// The type of the Scope entity. + /// An . + public IOpenIddictScopeStore Get() where TScope : class { - private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - private readonly IServiceProvider _provider; - - public OpenIddictMongoDbScopeStoreResolver(IServiceProvider provider) - => _provider = provider; - - /// - /// Returns a scope store compatible with the specified scope type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Scope entity. - /// An . - public IOpenIddictScopeStore Get() where TScope : class + var store = _provider.GetService>(); + if (store is not null) { - var store = _provider.GetService>(); - if (store is not null) - { - return store; - } + return store; + } - var type = _cache.GetOrAdd(typeof(TScope), key => + var type = _cache.GetOrAdd(typeof(TScope), key => + { + if (!typeof(OpenIddictMongoDbScope).IsAssignableFrom(key)) { - if (!typeof(OpenIddictMongoDbScope).IsAssignableFrom(key)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0259)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0259)); + } - return typeof(OpenIddictMongoDbScopeStore<>).MakeGenericType(key); - }); + return typeof(OpenIddictMongoDbScopeStore<>).MakeGenericType(key); + }); - return (IOpenIddictScopeStore) _provider.GetRequiredService(type); - } + return (IOpenIddictScopeStore) _provider.GetRequiredService(type); } } diff --git a/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbTokenStoreResolver.cs b/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbTokenStoreResolver.cs index 28432394..8943286f 100644 --- a/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbTokenStoreResolver.cs +++ b/src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbTokenStoreResolver.cs @@ -11,44 +11,43 @@ using OpenIddict.Abstractions; using OpenIddict.MongoDb.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Exposes a method allowing to resolve a token store. +/// +public class OpenIddictMongoDbTokenStoreResolver : IOpenIddictTokenStoreResolver { + private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + private readonly IServiceProvider _provider; + + public OpenIddictMongoDbTokenStoreResolver(IServiceProvider provider) + => _provider = provider; + /// - /// Exposes a method allowing to resolve a token store. + /// Returns a token store compatible with the specified token type or throws an + /// if no store can be built using the specified type. /// - public class OpenIddictMongoDbTokenStoreResolver : IOpenIddictTokenStoreResolver + /// The type of the Token entity. + /// An . + public IOpenIddictTokenStore Get() where TToken : class { - private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - private readonly IServiceProvider _provider; - - public OpenIddictMongoDbTokenStoreResolver(IServiceProvider provider) - => _provider = provider; - - /// - /// Returns a token store compatible with the specified token type or throws an - /// if no store can be built using the specified type. - /// - /// The type of the Token entity. - /// An . - public IOpenIddictTokenStore Get() where TToken : class + var store = _provider.GetService>(); + if (store is not null) { - var store = _provider.GetService>(); - if (store is not null) - { - return store; - } + return store; + } - var type = _cache.GetOrAdd(typeof(TToken), key => + var type = _cache.GetOrAdd(typeof(TToken), key => + { + if (!typeof(OpenIddictMongoDbToken).IsAssignableFrom(key)) { - if (!typeof(OpenIddictMongoDbToken).IsAssignableFrom(key)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0260)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0260)); + } - return typeof(OpenIddictMongoDbTokenStore<>).MakeGenericType(key); - }); + return typeof(OpenIddictMongoDbTokenStore<>).MakeGenericType(key); + }); - return (IOpenIddictTokenStore) _provider.GetRequiredService(type); - } + return (IOpenIddictTokenStore) _provider.GetRequiredService(type); } } diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs index 565ee6e2..c6947184 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs @@ -24,656 +24,655 @@ using OpenIddict.Abstractions; using OpenIddict.MongoDb.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Provides methods allowing to manage the applications stored in a database. +/// +/// The type of the Application entity. +public class OpenIddictMongoDbApplicationStore : IOpenIddictApplicationStore + where TApplication : OpenIddictMongoDbApplication { + public OpenIddictMongoDbApplicationStore( + IOpenIddictMongoDbContext context, + IOptionsMonitor options) + { + Context = context; + Options = options; + } + + /// + /// Gets the database context associated with the current store. + /// + protected IOpenIddictMongoDbContext Context { get; } + /// - /// Provides methods allowing to manage the applications stored in a database. + /// Gets the options associated with the current store. /// - /// The type of the Application entity. - public class OpenIddictMongoDbApplicationStore : IOpenIddictApplicationStore - where TApplication : OpenIddictMongoDbApplication + protected IOptionsMonitor Options { get; } + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); + + return await collection.CountDocumentsAsync(FilterDefinition.Empty, null, cancellationToken); + } + + /// + public virtual async ValueTask CountAsync( + Func, IQueryable> query, CancellationToken cancellationToken) { - public OpenIddictMongoDbApplicationStore( - IOpenIddictMongoDbContext context, - IOptionsMonitor options) + if (query is null) { - Context = context; - Options = options; + throw new ArgumentNullException(nameof(query)); } - /// - /// Gets the database context associated with the current store. - /// - protected IOpenIddictMongoDbContext Context { get; } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } + return await ((IMongoQueryable) query(collection.AsQueryable())).LongCountAsync(cancellationToken); + } - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + /// + public virtual async ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - - return await collection.CountDocumentsAsync(FilterDefinition.Empty, null, cancellationToken); + throw new ArgumentNullException(nameof(application)); } - /// - public virtual async ValueTask CountAsync( - Func, IQueryable> query, CancellationToken cancellationToken) + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); + + await collection.InsertOneAsync(application, null, cancellationToken); + } + + /// + public virtual async ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(application)); + } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - return await ((IMongoQueryable) query(collection.AsQueryable())).LongCountAsync(cancellationToken); + if ((await collection.DeleteOneAsync(entity => + entity.Id == application.Id && + entity.ConcurrencyToken == application.ConcurrencyToken, cancellationToken)).DeletedCount == 0) + { + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239)); } - /// - public virtual async ValueTask CreateAsync(TApplication application, CancellationToken cancellationToken) + // Delete the authorizations associated with the application. + await database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName) + .DeleteManyAsync(authorization => authorization.ApplicationId == application.Id, cancellationToken); + + // Delete the tokens associated with the application. + await database.GetCollection(Options.CurrentValue.TokensCollectionName) + .DeleteManyAsync(token => token.ApplicationId == application.Id, cancellationToken); + } + + /// + public virtual async ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - await collection.InsertOneAsync(application, null, cancellationToken); + return await collection.Find(application => application.ClientId == identifier).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual async ValueTask DeleteAsync(TApplication application, CancellationToken cancellationToken) + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); + + return await collection.Find(application => application.Id == + ObjectId.Parse(identifier)).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindByPostLogoutRedirectUriAsync( + string address, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(address)) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } + + return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - if ((await collection.DeleteOneAsync(entity => - entity.Id == application.Id && - entity.ConcurrencyToken == application.ConcurrencyToken, cancellationToken)).DeletedCount == 0) + await foreach (var application in collection.Find(application => + application.PostLogoutRedirectUris.Contains(address)).ToAsyncEnumerable(cancellationToken)) { - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239)); + yield return application; } - - // Delete the authorizations associated with the application. - await database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName) - .DeleteManyAsync(authorization => authorization.ApplicationId == application.Id, cancellationToken); - - // Delete the tokens associated with the application. - await database.GetCollection(Options.CurrentValue.TokensCollectionName) - .DeleteManyAsync(token => token.ApplicationId == application.Id, cancellationToken); } + } - /// - public virtual async ValueTask FindByClientIdAsync(string identifier, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindByRedirectUriAsync( + string address, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(address)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } + + return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - return await collection.Find(application => application.ClientId == identifier).FirstOrDefaultAsync(cancellationToken); + await foreach (var application in collection.Find(application => + application.RedirectUris.Contains(address)).ToAsyncEnumerable(cancellationToken)) + { + yield return application; + } } + } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentNullException(nameof(query)); + } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - return await collection.Find(application => application.Id == - ObjectId.Parse(identifier)).FirstOrDefaultAsync(cancellationToken); - } + return await ((IMongoQueryable) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(cancellationToken); + } - /// - public virtual IAsyncEnumerable FindByPostLogoutRedirectUriAsync( - string address, CancellationToken cancellationToken) + /// + public virtual ValueTask GetClientIdAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } + throw new ArgumentNullException(nameof(application)); + } - return ExecuteAsync(cancellationToken); + return new ValueTask(application.ClientId); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - - await foreach (var application in collection.Find(application => - application.PostLogoutRedirectUris.Contains(address)).ToAsyncEnumerable(cancellationToken)) - { - yield return application; - } - } + /// + public virtual ValueTask GetClientSecretAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual IAsyncEnumerable FindByRedirectUriAsync( - string address, CancellationToken cancellationToken) + return new ValueTask(application.ClientSecret); + } + + /// + public virtual ValueTask GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } + throw new ArgumentNullException(nameof(application)); + } - return ExecuteAsync(cancellationToken); + return new ValueTask(application.Type); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - - await foreach (var application in collection.Find(application => - application.RedirectUris.Contains(address)).ToAsyncEnumerable(cancellationToken)) - { - yield return application; - } - } + /// + public virtual ValueTask GetConsentTypeAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + return new ValueTask(application.ConsentType); + } + + /// + public virtual ValueTask GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(application)); + } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); + return new ValueTask(application.DisplayName); + } - return await ((IMongoQueryable) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(cancellationToken); + /// + public virtual ValueTask> GetDisplayNamesAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetClientIdAsync(TApplication application, CancellationToken cancellationToken) + if (application.DisplayNames is null || application.DisplayNames.Count == 0) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - return new ValueTask(application.ClientId); + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask GetClientSecretAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask>(application.DisplayNames.ToImmutableDictionary()); + } - return new ValueTask(application.ClientSecret); + /// + public virtual ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask(application.Id.ToString()); + } - return new ValueTask(application.Type); + /// + public virtual ValueTask> GetPermissionsAsync( + TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetConsentTypeAsync(TApplication application, CancellationToken cancellationToken) + if (application.Permissions is null || application.Permissions.Count == 0) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - return new ValueTask(application.ConsentType); + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask>(application.Permissions.ToImmutableArray()); + } - return new ValueTask(application.DisplayName); + /// + public virtual ValueTask> GetPostLogoutRedirectUrisAsync( + TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask> GetDisplayNamesAsync(TApplication application, CancellationToken cancellationToken) + if (application.PostLogoutRedirectUris is null || application.PostLogoutRedirectUris.Count == 0) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask>(ImmutableArray.Create()); + } - if (application.DisplayNames is null || application.DisplayNames.Count == 0) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + return new ValueTask>(application.PostLogoutRedirectUris.ToImmutableArray()); + } - return new ValueTask>(application.DisplayNames.ToImmutableDictionary()); + /// + public virtual ValueTask> GetPropertiesAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask GetIdAsync(TApplication application, CancellationToken cancellationToken) + if (application.Properties is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - return new ValueTask(application.Id.ToString()); + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask> GetPermissionsAsync( - TApplication application, CancellationToken cancellationToken) + using var document = JsonDocument.Parse(application.Properties.ToJson()); + var builder = ImmutableDictionary.CreateBuilder(); + + foreach (var property in document.RootElement.EnumerateObject()) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + builder[property.Name] = property.Value.Clone(); + } - if (application.Permissions is null || application.Permissions.Count == 0) - { - return new ValueTask>(ImmutableArray.Create()); - } + return new ValueTask>(builder.ToImmutable()); + } - return new ValueTask>(application.Permissions.ToImmutableArray()); + /// + public virtual ValueTask> GetRedirectUrisAsync( + TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask> GetPostLogoutRedirectUrisAsync( - TApplication application, CancellationToken cancellationToken) + if (application.RedirectUris is null || application.RedirectUris.Count == 0) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask>(ImmutableArray.Create()); + } - if (application.PostLogoutRedirectUris is null || application.PostLogoutRedirectUris.Count == 0) - { - return new ValueTask>(ImmutableArray.Create()); - } + return new ValueTask>(application.RedirectUris.ToImmutableArray()); + } - return new ValueTask>(application.PostLogoutRedirectUris.ToImmutableArray()); + /// + public virtual ValueTask> GetRequirementsAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask> GetPropertiesAsync(TApplication application, CancellationToken cancellationToken) + if (application.Requirements is null || application.Requirements.Count == 0) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (application.Properties is null) - { - return new ValueTask>(ImmutableDictionary.Create()); - } - - using var document = JsonDocument.Parse(application.Properties.ToJson()); - var builder = ImmutableDictionary.CreateBuilder(); + return new ValueTask>(ImmutableArray.Create()); + } - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return new ValueTask>(application.Requirements.ToImmutableArray()); + } - return new ValueTask>(builder.ToImmutable()); + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try + { + return new ValueTask(Activator.CreateInstance()); } - /// - public virtual ValueTask> GetRedirectUrisAsync( - TApplication application, CancellationToken cancellationToken) + catch (MemberAccessException exception) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0240), exception))); + } + } - if (application.RedirectUris is null || application.RedirectUris.Count == 0) - { - return new ValueTask>(ImmutableArray.Create()); - } + /// + public virtual async IAsyncEnumerable ListAsync( + int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - return new ValueTask>(application.RedirectUris.ToImmutableArray()); - } + var query = (IMongoQueryable) collection.AsQueryable().OrderBy(application => application.Id); - /// - public virtual ValueTask> GetRequirementsAsync(TApplication application, CancellationToken cancellationToken) + if (offset.HasValue) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (application.Requirements is null || application.Requirements.Count == 0) - { - return new ValueTask>(ImmutableArray.Create()); - } + query = query.Skip(offset.Value); + } - return new ValueTask>(application.Requirements.ToImmutableArray()); + if (count.HasValue) + { + query = query.Take(count.Value); } - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + await foreach (var application in ((IAsyncCursorSource) query).ToAsyncEnumerable(cancellationToken)) { - try - { - return new ValueTask(Activator.CreateInstance()); - } + yield return application; + } + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0240), exception))); - } + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual async IAsyncEnumerable ListAsync( - int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - var query = (IMongoQueryable) collection.AsQueryable().OrderBy(application => application.Id); - - if (offset.HasValue) + await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable(cancellationToken)) { - query = query.Skip(offset.Value); - } - - if (count.HasValue) - { - query = query.Take(count.Value); - } - - await foreach (var application in ((IAsyncCursorSource) query).ToAsyncEnumerable(cancellationToken)) - { - yield return application; + yield return element; } } + } - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + /// + public virtual ValueTask SetClientIdAsync(TApplication application, + string? identifier, CancellationToken cancellationToken) + { + if (application is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(application)); + } - return ExecuteAsync(cancellationToken); + application.ClientId = identifier; - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); + return default; + } - await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable(cancellationToken)) - { - yield return element; - } - } + /// + public virtual ValueTask SetClientSecretAsync(TApplication application, + string? secret, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask SetClientIdAsync(TApplication application, - string? identifier, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + application.ClientSecret = secret; - application.ClientId = identifier; + return default; + } - return default; + /// + public virtual ValueTask SetClientTypeAsync(TApplication application, + string? type, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask SetClientSecretAsync(TApplication application, - string? secret, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + application.Type = type; - application.ClientSecret = secret; + return default; + } - return default; + /// + public virtual ValueTask SetConsentTypeAsync(TApplication application, + string? type, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask SetClientTypeAsync(TApplication application, - string? type, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + application.ConsentType = type; - application.Type = type; + return default; + } - return default; + /// + public virtual ValueTask SetDisplayNameAsync(TApplication application, + string? name, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask SetConsentTypeAsync(TApplication application, - string? type, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + application.DisplayName = name; - application.ConsentType = type; + return default; + } - return default; + /// + public virtual ValueTask SetDisplayNamesAsync(TApplication application, + ImmutableDictionary names, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask SetDisplayNameAsync(TApplication application, - string? name, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + application.DisplayNames = names; - application.DisplayName = name; + return default; + } - return default; + /// + public virtual ValueTask SetPermissionsAsync(TApplication application, ImmutableArray permissions, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask SetDisplayNamesAsync(TApplication application, - ImmutableDictionary names, CancellationToken cancellationToken) + if (permissions.IsDefaultOrEmpty) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - application.DisplayNames = names; + application.Permissions = ImmutableList.Create(); return default; } - /// - public virtual ValueTask SetPermissionsAsync(TApplication application, ImmutableArray permissions, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + application.Permissions = permissions.ToImmutableList(); - if (permissions.IsDefaultOrEmpty) - { - application.Permissions = ImmutableList.Create(); + return default; + } - return default; - } + /// + public virtual ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, + ImmutableArray addresses, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.Permissions = permissions.ToImmutableList(); + if (addresses.IsDefaultOrEmpty) + { + application.PostLogoutRedirectUris = ImmutableList.Create(); return default; } - /// - public virtual ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, - ImmutableArray addresses, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + application.PostLogoutRedirectUris = addresses.ToImmutableList(); - if (addresses.IsDefaultOrEmpty) - { - application.PostLogoutRedirectUris = ImmutableList.Create(); + return default; + } - return default; - } + /// + public virtual ValueTask SetPropertiesAsync(TApplication application, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.PostLogoutRedirectUris = addresses.ToImmutableList(); + if (properties is null || properties.IsEmpty) + { + application.Properties = null; return default; } - /// - public virtual ValueTask SetPropertiesAsync(TApplication application, - ImmutableDictionary properties, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (properties is null || properties.IsEmpty) - { - application.Properties = null; - - return default; - } + writer.WriteStartObject(); - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); - - writer.WriteStartObject(); + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + writer.WriteEndObject(); + writer.Flush(); - writer.WriteEndObject(); - writer.Flush(); + application.Properties = BsonDocument.Parse(Encoding.UTF8.GetString(stream.ToArray())); - application.Properties = BsonDocument.Parse(Encoding.UTF8.GetString(stream.ToArray())); + return default; + } - return default; + /// + public virtual ValueTask SetRedirectUrisAsync(TApplication application, + ImmutableArray addresses, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); } - /// - public virtual ValueTask SetRedirectUrisAsync(TApplication application, - ImmutableArray addresses, CancellationToken cancellationToken) + if (addresses.IsDefaultOrEmpty) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } - - if (addresses.IsDefaultOrEmpty) - { - application.RedirectUris = ImmutableList.Create(); - - return default; - } - - application.RedirectUris = addresses.ToImmutableList(); + application.RedirectUris = ImmutableList.Create(); return default; } - /// - public virtual ValueTask SetRequirementsAsync(TApplication application, - ImmutableArray requirements, CancellationToken cancellationToken) - { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + application.RedirectUris = addresses.ToImmutableList(); - if (requirements.IsDefaultOrEmpty) - { - application.Requirements = ImmutableList.Create(); + return default; + } - return default; - } + /// + public virtual ValueTask SetRequirementsAsync(TApplication application, + ImmutableArray requirements, CancellationToken cancellationToken) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } - application.Requirements = requirements.ToImmutableList(); + if (requirements.IsDefaultOrEmpty) + { + application.Requirements = ImmutableList.Create(); return default; } - /// - public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken) + application.Requirements = requirements.ToImmutableList(); + + return default; + } + + /// + public virtual async ValueTask UpdateAsync(TApplication application, CancellationToken cancellationToken) + { + if (application is null) { - if (application is null) - { - throw new ArgumentNullException(nameof(application)); - } + throw new ArgumentNullException(nameof(application)); + } - // Generate a new concurrency token and attach it - // to the application before persisting the changes. - var timestamp = application.ConcurrencyToken; - application.ConcurrencyToken = Guid.NewGuid().ToString(); + // Generate a new concurrency token and attach it + // to the application before persisting the changes. + var timestamp = application.ConcurrencyToken; + application.ConcurrencyToken = Guid.NewGuid().ToString(); - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - if ((await collection.ReplaceOneAsync(entity => - entity.Id == application.Id && - entity.ConcurrencyToken == timestamp, application, null as ReplaceOptions, cancellationToken)).MatchedCount == 0) - { - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239)); - } + if ((await collection.ReplaceOneAsync(entity => + entity.Id == application.Id && + entity.ConcurrencyToken == timestamp, application, null as ReplaceOptions, cancellationToken)).MatchedCount == 0) + { + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0239)); } } -} \ No newline at end of file +} diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs index 288cd893..0ce8c9e0 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs @@ -24,717 +24,716 @@ using OpenIddict.MongoDb.Models; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Provides methods allowing to manage the authorizations stored in a database. +/// +/// The type of the Authorization entity. +public class OpenIddictMongoDbAuthorizationStore : IOpenIddictAuthorizationStore + where TAuthorization : OpenIddictMongoDbAuthorization { + public OpenIddictMongoDbAuthorizationStore( + IOpenIddictMongoDbContext context, + IOptionsMonitor options) + { + Context = context; + Options = options; + } + + /// + /// Gets the database context associated with the current store. + /// + protected IOpenIddictMongoDbContext Context { get; } + /// - /// Provides methods allowing to manage the authorizations stored in a database. + /// Gets the options associated with the current store. /// - /// The type of the Authorization entity. - public class OpenIddictMongoDbAuthorizationStore : IOpenIddictAuthorizationStore - where TAuthorization : OpenIddictMongoDbAuthorization + protected IOptionsMonitor Options { get; } + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + + return await collection.CountDocumentsAsync(FilterDefinition.Empty, null, cancellationToken); + } + + /// + public virtual async ValueTask CountAsync( + Func, IQueryable> query, CancellationToken cancellationToken) { - public OpenIddictMongoDbAuthorizationStore( - IOpenIddictMongoDbContext context, - IOptionsMonitor options) + if (query is null) { - Context = context; - Options = options; + throw new ArgumentNullException(nameof(query)); } - /// - /// Gets the database context associated with the current store. - /// - protected IOpenIddictMongoDbContext Context { get; } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } + return await ((IMongoQueryable) query(collection.AsQueryable())).LongCountAsync(cancellationToken); + } - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + /// + public virtual async ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - return await collection.CountDocumentsAsync(FilterDefinition.Empty, null, cancellationToken); + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual async ValueTask CountAsync( - Func, IQueryable> query, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + await collection.InsertOneAsync(authorization, null, cancellationToken); + } - return await ((IMongoQueryable) query(collection.AsQueryable())).LongCountAsync(cancellationToken); + /// + public virtual async ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual async ValueTask CreateAsync(TAuthorization authorization, CancellationToken cancellationToken) + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + + if ((await collection.DeleteOneAsync(entity => + entity.Id == authorization.Id && + entity.ConcurrencyToken == authorization.ConcurrencyToken, cancellationToken)).DeletedCount == 0) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241)); + } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + // Delete the tokens associated with the authorization. + await database.GetCollection(Options.CurrentValue.TokensCollectionName) + .DeleteManyAsync(token => token.AuthorizationId == authorization.Id, cancellationToken); + } - await collection.InsertOneAsync(authorization, null, cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual async ValueTask DeleteAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - if ((await collection.DeleteOneAsync(entity => - entity.Id == authorization.Id && - entity.ConcurrencyToken == authorization.ConcurrencyToken, cancellationToken)).DeletedCount == 0) + await foreach (var authorization in collection.Find(authorization => + authorization.Subject == subject && + authorization.ApplicationId == ObjectId.Parse(client)).ToAsyncEnumerable(cancellationToken)) { - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241)); + yield return authorization; } - - // Delete the tokens associated with the authorization. - await database.GetCollection(Options.CurrentValue.TokensCollectionName) - .DeleteManyAsync(token => token.AuthorizationId == authorization.Id, cancellationToken); } + } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - await foreach (var authorization in collection.Find(authorization => - authorization.Subject == subject && - authorization.ApplicationId == ObjectId.Parse(client)).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } - } + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(status)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + return ExecuteAsync(cancellationToken); - if (string.IsNullOrEmpty(status)) + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + + await foreach (var authorization in collection.Find(authorization => + authorization.Subject == subject && + authorization.ApplicationId == ObjectId.Parse(client) && + authorization.Status == status).ToAsyncEnumerable(cancellationToken)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + yield return authorization; } + } + } - return ExecuteAsync(cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - await foreach (var authorization in collection.Find(authorization => - authorization.Subject == subject && - authorization.ApplicationId == ObjectId.Parse(client) && - authorization.Status == status).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(type)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + return ExecuteAsync(cancellationToken); - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - if (string.IsNullOrEmpty(type)) + await foreach (var authorization in collection.Find(authorization => + authorization.Subject == subject && + authorization.ApplicationId == ObjectId.Parse(client) && + authorization.Status == status && + authorization.Type == type).ToAsyncEnumerable(cancellationToken)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + yield return authorization; } + } + } - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - await foreach (var authorization in collection.Find(authorization => - authorization.Subject == subject && - authorization.ApplicationId == ObjectId.Parse(client) && - authorization.Status == status && - authorization.Type == type).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } - } + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, + ImmutableArray scopes, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, - ImmutableArray scopes, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + return ExecuteAsync(cancellationToken); - return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + // Note: Enumerable.All() is deliberately used without the extension method syntax to ensure + // ImmutableArrayExtensions.All() (which is not supported by MongoDB) is not used instead. + await foreach (var authorization in collection.Find(authorization => + authorization.Subject == subject && + authorization.ApplicationId == ObjectId.Parse(client) && + authorization.Status == status && + authorization.Type == type && + Enumerable.All(scopes, scope => authorization.Scopes.Contains(scope))).ToAsyncEnumerable(cancellationToken)) { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - // Note: Enumerable.All() is deliberately used without the extension method syntax to ensure - // ImmutableArrayExtensions.All() (which is not supported by MongoDB) is not used instead. - await foreach (var authorization in collection.Find(authorization => - authorization.Subject == subject && - authorization.ApplicationId == ObjectId.Parse(client) && - authorization.Status == status && - authorization.Type == type && - Enumerable.All(scopes, scope => authorization.Scopes.Contains(scope))).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } + yield return authorization; } } + } - /// - public virtual IAsyncEnumerable FindByApplicationIdAsync( - string identifier, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindByApplicationIdAsync( + string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } - - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - await foreach (var authorization in collection.Find(authorization => - authorization.ApplicationId == ObjectId.Parse(identifier)).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - return await collection.Find(authorization => authorization.Id == ObjectId.Parse(identifier)) - .FirstOrDefaultAsync(cancellationToken); + await foreach (var authorization in collection.Find(authorization => + authorization.ApplicationId == ObjectId.Parse(identifier)).ToAsyncEnumerable(cancellationToken)) + { + yield return authorization; + } } + } - /// - public virtual IAsyncEnumerable FindBySubjectAsync( - string subject, CancellationToken cancellationToken) + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - return ExecuteAsync(cancellationToken); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + return await collection.Find(authorization => authorization.Id == ObjectId.Parse(identifier)) + .FirstOrDefaultAsync(cancellationToken); + } - await foreach (var authorization in collection.Find(authorization => - authorization.Subject == subject).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } - } + /// + public virtual IAsyncEnumerable FindBySubjectAsync( + string subject, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual ValueTask GetApplicationIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - if (authorization.ApplicationId == ObjectId.Empty) + await foreach (var authorization in collection.Find(authorization => + authorization.Subject == subject).ToAsyncEnumerable(cancellationToken)) { - return new ValueTask(result: null); + yield return authorization; } - - return new ValueTask(authorization.ApplicationId.ToString()); } + } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + /// + public virtual ValueTask GetApplicationIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - return await ((IMongoQueryable) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(cancellationToken); + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (authorization.ApplicationId == ObjectId.Empty) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return new ValueTask(result: null); + } - if (authorization.CreationDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(authorization.ApplicationId.ToString()); + } - return new ValueTask(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc)); + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + + return await ((IMongoQueryable) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(cancellationToken); + } - return new ValueTask(authorization.Id.ToString()); + /// + public virtual ValueTask GetCreationDateAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask> GetPropertiesAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (authorization.CreationDate is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return new ValueTask(result: null); + } - if (authorization.Properties is null) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + return new ValueTask(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc)); + } - using var document = JsonDocument.Parse(authorization.Properties.ToJson()); - var builder = ImmutableDictionary.CreateBuilder(); + /// + public virtual ValueTask GetIdAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return new ValueTask(authorization.Id.ToString()); + } - return new ValueTask>(builder.ToImmutable()); + /// + public virtual ValueTask> GetPropertiesAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask> GetScopesAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (authorization.Properties is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return new ValueTask>(ImmutableDictionary.Create()); + } - if (authorization.Scopes is null || authorization.Scopes.Count == 0) - { - return new ValueTask>(ImmutableArray.Create()); - } + using var document = JsonDocument.Parse(authorization.Properties.ToJson()); + var builder = ImmutableDictionary.CreateBuilder(); - return new ValueTask>(authorization.Scopes.ToImmutableArray()); + foreach (var property in document.RootElement.EnumerateObject()) + { + builder[property.Name] = property.Value.Clone(); } - /// - public virtual ValueTask GetStatusAsync(TAuthorization authorization, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return new ValueTask>(builder.ToImmutable()); + } - return new ValueTask(authorization.Status); + /// + public virtual ValueTask> GetScopesAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask GetSubjectAsync(TAuthorization authorization, CancellationToken cancellationToken) + if (authorization.Scopes is null || authorization.Scopes.Count == 0) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - return new ValueTask(authorization.Subject); + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask GetTypeAsync(TAuthorization authorization, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + return new ValueTask>(authorization.Scopes.ToImmutableArray()); + } - return new ValueTask(authorization.Type); + /// + public virtual ValueTask GetStatusAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + return new ValueTask(authorization.Status); + } + + /// + public virtual ValueTask GetSubjectAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - try - { - return new ValueTask(Activator.CreateInstance()); - } + throw new ArgumentNullException(nameof(authorization)); + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0242), exception))); - } + return new ValueTask(authorization.Subject); + } + + /// + public virtual ValueTask GetTypeAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual async IAsyncEnumerable ListAsync( - int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) + return new ValueTask(authorization.Type); + } + + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + return new ValueTask(Activator.CreateInstance()); + } - var query = (IMongoQueryable) collection.AsQueryable().OrderBy(authorization => authorization.Id); + catch (MemberAccessException exception) + { + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0242), exception))); + } + } - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } + /// + public virtual async IAsyncEnumerable ListAsync( + int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - if (count.HasValue) - { - query = query.Take(count.Value); - } + var query = (IMongoQueryable) collection.AsQueryable().OrderBy(authorization => authorization.Id); - await foreach (var authorization in ((IAsyncCursorSource) query).ToAsyncEnumerable(cancellationToken)) - { - yield return authorization; - } + if (offset.HasValue) + { + query = query.Skip(offset.Value); } - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + if (count.HasValue) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } - - return ExecuteAsync(cancellationToken); + query = query.Take(count.Value); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + await foreach (var authorization in ((IAsyncCursorSource) query).ToAsyncEnumerable(cancellationToken)) + { + yield return authorization; + } + } - await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable(cancellationToken)) - { - yield return element; - } - } + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - // Note: directly deleting the resulting set of an aggregate query is not supported by MongoDB. - // To work around this limitation, the authorization identifiers are stored in an intermediate - // list and delete requests are sent to remove the documents corresponding to these identifiers. - - var identifiers = - await (from authorization in collection.AsQueryable() - join token in database.GetCollection(Options.CurrentValue.TokensCollectionName).AsQueryable() - on authorization.Id equals token.AuthorizationId into tokens - where authorization.CreationDate < threshold.UtcDateTime - where authorization.Status != Statuses.Valid || - (authorization.Type == AuthorizationTypes.AdHoc && !tokens.Any()) - select authorization.Id).ToListAsync(cancellationToken); - - // Note: to avoid generating delete requests with very large filters, a buffer is used here and the - // maximum number of elements that can be removed by a single call to PruneAsync() is deliberately limited. - foreach (var buffer in Buffer(identifiers.Take(1_000_000), 1_000)) + await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable(cancellationToken)) { - await collection.DeleteManyAsync(authorization => buffer.Contains(authorization.Id), cancellationToken); + yield return element; } + } + } - static IEnumerable> Buffer(IEnumerable source, int count) - { - List? buffer = null; - - foreach (var element in source) - { - buffer ??= new List(capacity: 1); - buffer.Add(element); + /// + public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - if (buffer.Count == count) - { - yield return buffer; + // Note: directly deleting the resulting set of an aggregate query is not supported by MongoDB. + // To work around this limitation, the authorization identifiers are stored in an intermediate + // list and delete requests are sent to remove the documents corresponding to these identifiers. - buffer = null; - } - } + var identifiers = + await (from authorization in collection.AsQueryable() + join token in database.GetCollection(Options.CurrentValue.TokensCollectionName).AsQueryable() + on authorization.Id equals token.AuthorizationId into tokens + where authorization.CreationDate < threshold.UtcDateTime + where authorization.Status != Statuses.Valid || + (authorization.Type == AuthorizationTypes.AdHoc && !tokens.Any()) + select authorization.Id).ToListAsync(cancellationToken); - if (buffer is not null) - { - yield return buffer; - } - } + // Note: to avoid generating delete requests with very large filters, a buffer is used here and the + // maximum number of elements that can be removed by a single call to PruneAsync() is deliberately limited. + foreach (var buffer in Buffer(identifiers.Take(1_000_000), 1_000)) + { + await collection.DeleteManyAsync(authorization => buffer.Contains(authorization.Id), cancellationToken); } - /// - public virtual ValueTask SetApplicationIdAsync(TAuthorization authorization, - string? identifier, CancellationToken cancellationToken) + static IEnumerable> Buffer(IEnumerable source, int count) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + List? buffer = null; - if (!string.IsNullOrEmpty(identifier)) + foreach (var element in source) { - authorization.ApplicationId = ObjectId.Parse(identifier); + buffer ??= new List(capacity: 1); + buffer.Add(element); + + if (buffer.Count == count) + { + yield return buffer; + + buffer = null; + } } - else + if (buffer is not null) { - authorization.ApplicationId = ObjectId.Empty; + yield return buffer; } - - return default; } + } - /// - public virtual ValueTask SetCreationDateAsync(TAuthorization authorization, - DateTimeOffset? date, CancellationToken cancellationToken) + /// + public virtual ValueTask SetApplicationIdAsync(TAuthorization authorization, + string? identifier, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } - - authorization.CreationDate = date?.UtcDateTime; - - return default; + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask SetPropertiesAsync(TAuthorization authorization, - ImmutableDictionary properties, CancellationToken cancellationToken) + if (!string.IsNullOrEmpty(identifier)) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + authorization.ApplicationId = ObjectId.Parse(identifier); + } - if (properties is null || properties.IsEmpty) - { - authorization.Properties = null; + else + { + authorization.ApplicationId = ObjectId.Empty; + } - return default; - } + return default; + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + /// + public virtual ValueTask SetCreationDateAsync(TAuthorization authorization, + DateTimeOffset? date, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - writer.WriteStartObject(); + authorization.CreationDate = date?.UtcDateTime; - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + return default; + } - writer.WriteEndObject(); - writer.Flush(); + /// + public virtual ValueTask SetPropertiesAsync(TAuthorization authorization, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); + } - authorization.Properties = BsonDocument.Parse(Encoding.UTF8.GetString(stream.ToArray())); + if (properties is null || properties.IsEmpty) + { + authorization.Properties = null; return default; } - /// - public virtual ValueTask SetScopesAsync(TAuthorization authorization, - ImmutableArray scopes, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - if (scopes.IsDefaultOrEmpty) - { - authorization.Scopes = ImmutableList.Create(); + writer.WriteStartObject(); - return default; - } + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } - authorization.Scopes = scopes.ToImmutableList(); + writer.WriteEndObject(); + writer.Flush(); - return default; - } + authorization.Properties = BsonDocument.Parse(Encoding.UTF8.GetString(stream.ToArray())); - /// - public virtual ValueTask SetStatusAsync(TAuthorization authorization, string? status, CancellationToken cancellationToken) + return default; + } + + /// + public virtual ValueTask SetScopesAsync(TAuthorization authorization, + ImmutableArray scopes, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - authorization.Status = status; + if (scopes.IsDefaultOrEmpty) + { + authorization.Scopes = ImmutableList.Create(); return default; } - /// - public virtual ValueTask SetSubjectAsync(TAuthorization authorization, string? subject, CancellationToken cancellationToken) - { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + authorization.Scopes = scopes.ToImmutableList(); - authorization.Subject = subject; + return default; + } - return default; + /// + public virtual ValueTask SetStatusAsync(TAuthorization authorization, string? status, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual ValueTask SetTypeAsync(TAuthorization authorization, string? type, CancellationToken cancellationToken) + authorization.Status = status; + + return default; + } + + /// + public virtual ValueTask SetSubjectAsync(TAuthorization authorization, string? subject, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - authorization.Type = type; + authorization.Subject = subject; - return default; + return default; + } + + /// + public virtual ValueTask SetTypeAsync(TAuthorization authorization, string? type, CancellationToken cancellationToken) + { + if (authorization is null) + { + throw new ArgumentNullException(nameof(authorization)); } - /// - public virtual async ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken) + authorization.Type = type; + + return default; + } + + /// + public virtual async ValueTask UpdateAsync(TAuthorization authorization, CancellationToken cancellationToken) + { + if (authorization is null) { - if (authorization is null) - { - throw new ArgumentNullException(nameof(authorization)); - } + throw new ArgumentNullException(nameof(authorization)); + } - // Generate a new concurrency token and attach it - // to the authorization before persisting the changes. - var timestamp = authorization.ConcurrencyToken; - authorization.ConcurrencyToken = Guid.NewGuid().ToString(); + // Generate a new concurrency token and attach it + // to the authorization before persisting the changes. + var timestamp = authorization.ConcurrencyToken; + authorization.ConcurrencyToken = Guid.NewGuid().ToString(); - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - if ((await collection.ReplaceOneAsync(entity => - entity.Id == authorization.Id && - entity.ConcurrencyToken == timestamp, authorization, null as ReplaceOptions, cancellationToken)).MatchedCount == 0) - { - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241)); - } + if ((await collection.ReplaceOneAsync(entity => + entity.Id == authorization.Id && + entity.ConcurrencyToken == timestamp, authorization, null as ReplaceOptions, cancellationToken)).MatchedCount == 0) + { + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0241)); } } -} \ No newline at end of file +} diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs index 58f44181..1ab880e7 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs @@ -24,508 +24,507 @@ using OpenIddict.Abstractions; using OpenIddict.MongoDb.Models; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Provides methods allowing to manage the scopes stored in a database. +/// +/// The type of the Scope entity. +public class OpenIddictMongoDbScopeStore : IOpenIddictScopeStore + where TScope : OpenIddictMongoDbScope { + public OpenIddictMongoDbScopeStore( + IOpenIddictMongoDbContext context, + IOptionsMonitor options) + { + Context = context; + Options = options; + } + + /// + /// Gets the database context associated with the current store. + /// + protected IOpenIddictMongoDbContext Context { get; } + /// - /// Provides methods allowing to manage the scopes stored in a database. + /// Gets the options associated with the current store. /// - /// The type of the Scope entity. - public class OpenIddictMongoDbScopeStore : IOpenIddictScopeStore - where TScope : OpenIddictMongoDbScope + protected IOptionsMonitor Options { get; } + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + + return await collection.CountDocumentsAsync(FilterDefinition.Empty, null, cancellationToken); + } + + /// + public virtual async ValueTask CountAsync( + Func, IQueryable> query, CancellationToken cancellationToken) { - public OpenIddictMongoDbScopeStore( - IOpenIddictMongoDbContext context, - IOptionsMonitor options) + if (query is null) { - Context = context; - Options = options; + throw new ArgumentNullException(nameof(query)); } - /// - /// Gets the database context associated with the current store. - /// - protected IOpenIddictMongoDbContext Context { get; } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } + return await ((IMongoQueryable) query(collection.AsQueryable())).LongCountAsync(cancellationToken); + } - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + /// + public virtual async ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - - return await collection.CountDocumentsAsync(FilterDefinition.Empty, null, cancellationToken); + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual async ValueTask CountAsync( - Func, IQueryable> query, CancellationToken cancellationToken) + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + + await collection.InsertOneAsync(scope, null, cancellationToken); + } + + /// + public virtual async ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(scope)); + } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - return await ((IMongoQueryable) query(collection.AsQueryable())).LongCountAsync(cancellationToken); + if ((await collection.DeleteOneAsync(entity => + entity.Id == scope.Id && + entity.ConcurrencyToken == scope.ConcurrencyToken, cancellationToken)).DeletedCount == 0) + { + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245)); } + } - /// - public virtual async ValueTask CreateAsync(TScope scope, CancellationToken cancellationToken) + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + + return await collection.Find(scope => scope.Id == ObjectId.Parse(identifier)).FirstOrDefaultAsync(cancellationToken); + } - await collection.InsertOneAsync(scope, null, cancellationToken); + /// + public virtual async ValueTask FindByNameAsync(string name, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); } - /// - public virtual async ValueTask DeleteAsync(TScope scope, CancellationToken cancellationToken) + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + + return await collection.Find(scope => scope.Name == name).FirstOrDefaultAsync(cancellationToken); + } + + /// + public virtual IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken) + { + if (names.Any(name => string.IsNullOrEmpty(name))) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); + } + + return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - if ((await collection.DeleteOneAsync(entity => - entity.Id == scope.Id && - entity.ConcurrencyToken == scope.ConcurrencyToken, cancellationToken)).DeletedCount == 0) + // Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure + // ImmutableArray.Contains() (which is not fully supported by MongoDB) is not used instead. + await foreach (var scope in collection.Find(scope => Enumerable.Contains(names, scope.Name)).ToAsyncEnumerable(cancellationToken)) { - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245)); + yield return scope; } } + } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(resource)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } - - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - - return await collection.Find(scope => scope.Id == ObjectId.Parse(identifier)).FirstOrDefaultAsync(cancellationToken); + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); } - /// - public virtual async ValueTask FindByNameAsync(string name, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name)); - } + return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - return await collection.Find(scope => scope.Name == name).FirstOrDefaultAsync(cancellationToken); + await foreach (var scope in collection.Find(scope => scope.Resources.Contains(resource)).ToAsyncEnumerable(cancellationToken)) + { + yield return scope; + } } + } - /// - public virtual IAsyncEnumerable FindByNamesAsync(ImmutableArray names, CancellationToken cancellationToken) + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) { - if (names.Any(name => string.IsNullOrEmpty(name))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names)); - } + throw new ArgumentNullException(nameof(query)); + } - return ExecuteAsync(cancellationToken); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - - // Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure - // ImmutableArray.Contains() (which is not fully supported by MongoDB) is not used instead. - await foreach (var scope in collection.Find(scope => Enumerable.Contains(names, scope.Name)).ToAsyncEnumerable(cancellationToken)) - { - yield return scope; - } - } - } + return await ((IMongoQueryable) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(cancellationToken); + } - /// - public virtual IAsyncEnumerable FindByResourceAsync(string resource, CancellationToken cancellationToken) + /// + public virtual ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); - } - - return ExecuteAsync(cancellationToken); + throw new ArgumentNullException(nameof(scope)); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + return new ValueTask(scope.Description); + } - await foreach (var scope in collection.Find(scope => scope.Resources.Contains(resource)).ToAsyncEnumerable(cancellationToken)) - { - yield return scope; - } - } + /// + public virtual ValueTask> GetDescriptionsAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + if (scope.Descriptions is null || scope.Descriptions.Count == 0) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + return new ValueTask>(ImmutableDictionary.Create()); + } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + return new ValueTask>(scope.Descriptions.ToImmutableDictionary()); + } - return await ((IMongoQueryable) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(cancellationToken); + /// + public virtual ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask GetDescriptionAsync(TScope scope, CancellationToken cancellationToken) - { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + return new ValueTask(scope.DisplayName); + } - return new ValueTask(scope.Description); + /// + public virtual ValueTask> GetDisplayNamesAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask> GetDescriptionsAsync(TScope scope, CancellationToken cancellationToken) + if (scope.DisplayNames is null || scope.DisplayNames.Count == 0) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + return new ValueTask>(ImmutableDictionary.Create()); + } - if (scope.Descriptions is null || scope.Descriptions.Count == 0) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + return new ValueTask>(scope.DisplayNames.ToImmutableDictionary()); + } - return new ValueTask>(scope.Descriptions.ToImmutableDictionary()); + /// + public virtual ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask GetDisplayNameAsync(TScope scope, CancellationToken cancellationToken) + return new ValueTask(scope.Id.ToString()); + } + + /// + public virtual ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } + + return new ValueTask(scope.Name); + } - return new ValueTask(scope.DisplayName); + /// + public virtual ValueTask> GetPropertiesAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask> GetDisplayNamesAsync(TScope scope, CancellationToken cancellationToken) + if (scope.Properties is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + return new ValueTask>(ImmutableDictionary.Create()); + } - if (scope.DisplayNames is null || scope.DisplayNames.Count == 0) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + using var document = JsonDocument.Parse(scope.Properties.ToJson()); + var builder = ImmutableDictionary.CreateBuilder(); - return new ValueTask>(scope.DisplayNames.ToImmutableDictionary()); + foreach (var property in document.RootElement.EnumerateObject()) + { + builder[property.Name] = property.Value.Clone(); } - /// - public virtual ValueTask GetIdAsync(TScope scope, CancellationToken cancellationToken) - { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + return new ValueTask>(builder.ToImmutable()); + } - return new ValueTask(scope.Id.ToString()); + /// + public virtual ValueTask> GetResourcesAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask GetNameAsync(TScope scope, CancellationToken cancellationToken) + if (scope.Resources is null || scope.Resources.Count == 0) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - return new ValueTask(scope.Name); + return new ValueTask>(ImmutableArray.Create()); } - /// - public virtual ValueTask> GetPropertiesAsync(TScope scope, CancellationToken cancellationToken) + return new ValueTask>(scope.Resources.ToImmutableArray()); + } + + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + return new ValueTask(Activator.CreateInstance()); + } - if (scope.Properties is null) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + catch (MemberAccessException exception) + { + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0246), exception))); + } + } - using var document = JsonDocument.Parse(scope.Properties.ToJson()); - var builder = ImmutableDictionary.CreateBuilder(); + /// + public virtual async IAsyncEnumerable ListAsync( + int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + var query = (IMongoQueryable) collection.AsQueryable().OrderBy(scope => scope.Id); - return new ValueTask>(builder.ToImmutable()); + if (offset.HasValue) + { + query = query.Skip(offset.Value); } - /// - public virtual ValueTask> GetResourcesAsync(TScope scope, CancellationToken cancellationToken) + if (count.HasValue) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - if (scope.Resources is null || scope.Resources.Count == 0) - { - return new ValueTask>(ImmutableArray.Create()); - } - - return new ValueTask>(scope.Resources.ToImmutableArray()); + query = query.Take(count.Value); } - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + await foreach (var scope in ((IAsyncCursorSource) query).ToAsyncEnumerable(cancellationToken)) { - try - { - return new ValueTask(Activator.CreateInstance()); - } + yield return scope; + } + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0246), exception))); - } + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual async IAsyncEnumerable ListAsync( - int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - var query = (IMongoQueryable) collection.AsQueryable().OrderBy(scope => scope.Id); - - if (offset.HasValue) + await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable(cancellationToken)) { - query = query.Skip(offset.Value); - } - - if (count.HasValue) - { - query = query.Take(count.Value); - } - - await foreach (var scope in ((IAsyncCursorSource) query).ToAsyncEnumerable(cancellationToken)) - { - yield return scope; + yield return element; } } + } - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + /// + public virtual ValueTask SetDescriptionAsync(TScope scope, string? description, CancellationToken cancellationToken) + { + if (scope is null) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + throw new ArgumentNullException(nameof(scope)); + } - return ExecuteAsync(cancellationToken); + scope.Description = description; - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + return default; + } - await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable(cancellationToken)) - { - yield return element; - } - } + /// + public virtual ValueTask SetDescriptionsAsync(TScope scope, + ImmutableDictionary descriptions, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask SetDescriptionAsync(TScope scope, string? description, CancellationToken cancellationToken) - { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + scope.Descriptions = descriptions; - scope.Description = description; + return default; + } - return default; + /// + public virtual ValueTask SetDisplayNamesAsync(TScope scope, + ImmutableDictionary names, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask SetDescriptionsAsync(TScope scope, - ImmutableDictionary descriptions, CancellationToken cancellationToken) - { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + scope.DisplayNames = names; - scope.Descriptions = descriptions; + return default; + } - return default; + /// + public virtual ValueTask SetDisplayNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask SetDisplayNamesAsync(TScope scope, - ImmutableDictionary names, CancellationToken cancellationToken) - { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + scope.DisplayName = name; - scope.DisplayNames = names; + return default; + } - return default; + /// + public virtual ValueTask SetNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask SetDisplayNameAsync(TScope scope, string? name, CancellationToken cancellationToken) - { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + scope.Name = name; - scope.DisplayName = name; + return default; + } - return default; + /// + public virtual ValueTask SetPropertiesAsync(TScope scope, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask SetNameAsync(TScope scope, string? name, CancellationToken cancellationToken) + if (properties is null || properties.IsEmpty) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - scope.Name = name; + scope.Properties = null; return default; } - /// - public virtual ValueTask SetPropertiesAsync(TScope scope, - ImmutableDictionary properties, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - if (properties is null || properties.IsEmpty) - { - scope.Properties = null; + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - return default; - } - - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteStartObject(); - writer.WriteStartObject(); + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); + } - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + writer.WriteEndObject(); + writer.Flush(); - writer.WriteEndObject(); - writer.Flush(); + scope.Properties = BsonDocument.Parse(Encoding.UTF8.GetString(stream.ToArray())); - scope.Properties = BsonDocument.Parse(Encoding.UTF8.GetString(stream.ToArray())); + return default; + } - return default; + /// + public virtual ValueTask SetResourcesAsync(TScope scope, ImmutableArray resources, CancellationToken cancellationToken) + { + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); } - /// - public virtual ValueTask SetResourcesAsync(TScope scope, ImmutableArray resources, CancellationToken cancellationToken) + if (resources.IsDefaultOrEmpty) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - if (resources.IsDefaultOrEmpty) - { - scope.Resources = ImmutableList.Create(); - - return default; - } - - scope.Resources = resources.ToImmutableList(); + scope.Resources = ImmutableList.Create(); return default; } - /// - public virtual async ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken) + scope.Resources = resources.ToImmutableList(); + + return default; + } + + /// + public virtual async ValueTask UpdateAsync(TScope scope, CancellationToken cancellationToken) + { + if (scope is null) { - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } + throw new ArgumentNullException(nameof(scope)); + } - // Generate a new concurrency token and attach it - // to the scope before persisting the changes. - var timestamp = scope.ConcurrencyToken; - scope.ConcurrencyToken = Guid.NewGuid().ToString(); + // Generate a new concurrency token and attach it + // to the scope before persisting the changes. + var timestamp = scope.ConcurrencyToken; + scope.ConcurrencyToken = Guid.NewGuid().ToString(); - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - if ((await collection.ReplaceOneAsync(entity => - entity.Id == scope.Id && - entity.ConcurrencyToken == timestamp, scope, null as ReplaceOptions, cancellationToken)).MatchedCount == 0) - { - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245)); - } + if ((await collection.ReplaceOneAsync(entity => + entity.Id == scope.Id && + entity.ConcurrencyToken == timestamp, scope, null as ReplaceOptions, cancellationToken)).MatchedCount == 0) + { + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0245)); } } -} \ No newline at end of file +} diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs index e7ca7b6c..465b971b 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs @@ -24,804 +24,803 @@ using OpenIddict.MongoDb.Models; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb +namespace OpenIddict.MongoDb; + +/// +/// Provides methods allowing to manage the tokens stored in a database. +/// +/// The type of the Token entity. +public class OpenIddictMongoDbTokenStore : IOpenIddictTokenStore + where TToken : OpenIddictMongoDbToken { + public OpenIddictMongoDbTokenStore( + IOpenIddictMongoDbContext context, + IOptionsMonitor options) + { + Context = context; + Options = options; + } + /// - /// Provides methods allowing to manage the tokens stored in a database. + /// Gets the database context associated with the current store. /// - /// The type of the Token entity. - public class OpenIddictMongoDbTokenStore : IOpenIddictTokenStore - where TToken : OpenIddictMongoDbToken + protected IOpenIddictMongoDbContext Context { get; } + + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + + /// + public virtual async ValueTask CountAsync(CancellationToken cancellationToken) { - public OpenIddictMongoDbTokenStore( - IOpenIddictMongoDbContext context, - IOptionsMonitor options) + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + + return await collection.CountDocumentsAsync(FilterDefinition.Empty, null, cancellationToken); + } + + /// + public virtual async ValueTask CountAsync( + Func, IQueryable> query, CancellationToken cancellationToken) + { + if (query is null) { - Context = context; - Options = options; + throw new ArgumentNullException(nameof(query)); } - /// - /// Gets the database context associated with the current store. - /// - protected IOpenIddictMongoDbContext Context { get; } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - /// - /// Gets the options associated with the current store. - /// - protected IOptionsMonitor Options { get; } + return await ((IMongoQueryable) query(collection.AsQueryable())).LongCountAsync(cancellationToken); + } - /// - public virtual async ValueTask CountAsync(CancellationToken cancellationToken) + /// + public virtual async ValueTask CreateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - - return await collection.CountDocumentsAsync(FilterDefinition.Empty, null, cancellationToken); + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask CountAsync( - Func, IQueryable> query, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + await collection.InsertOneAsync(token, null, cancellationToken); + } - return await ((IMongoQueryable) query(collection.AsQueryable())).LongCountAsync(cancellationToken); + /// + public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask CreateAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + if ((await collection.DeleteOneAsync(entity => + entity.Id == token.Id && + entity.ConcurrencyToken == token.ConcurrencyToken, cancellationToken)).DeletedCount == 0) + { + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247)); + } + } - await collection.InsertOneAsync(token, null, cancellationToken); + /// + public virtual IAsyncEnumerable FindAsync(string subject, + string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual async ValueTask DeleteAsync(TToken token, CancellationToken cancellationToken) + if (string.IsNullOrEmpty(client)) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - if ((await collection.DeleteOneAsync(entity => - entity.Id == token.Id && - entity.ConcurrencyToken == token.ConcurrencyToken, cancellationToken)).DeletedCount == 0) + await foreach (var token in collection.Find(token => + token.ApplicationId == ObjectId.Parse(client) && + token.Subject == subject).ToAsyncEnumerable(cancellationToken)) { - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247)); + yield return token; } } + } - /// - public virtual IAsyncEnumerable FindAsync(string subject, - string client, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } - - if (string.IsNullOrEmpty(client)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); - } - - return ExecuteAsync(cancellationToken); + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - await foreach (var token in collection.Find(token => - token.ApplicationId == ObjectId.Parse(client) && - token.Subject == subject).ToAsyncEnumerable(cancellationToken)) - { - yield return token; - } - } + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - if (string.IsNullOrEmpty(client)) + await foreach (var token in collection.Find(token => + token.ApplicationId == ObjectId.Parse(client) && + token.Subject == subject && + token.Status == status).ToAsyncEnumerable(cancellationToken)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + yield return token; } + } + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + /// + public virtual IAsyncEnumerable FindAsync( + string subject, string client, + string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } - return ExecuteAsync(cancellationToken); + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } - await foreach (var token in collection.Find(token => - token.ApplicationId == ObjectId.Parse(client) && - token.Subject == subject && - token.Status == status).ToAsyncEnumerable(cancellationToken)) - { - yield return token; - } - } + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); } - /// - public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - if (string.IsNullOrEmpty(client)) + await foreach (var token in collection.Find(token => + token.ApplicationId == ObjectId.Parse(client) && + token.Subject == subject && + token.Status == status && + token.Type == type).ToAsyncEnumerable(cancellationToken)) { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + yield return token; } + } + } - if (string.IsNullOrEmpty(status)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); - } + /// + public virtual IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); - } + return ExecuteAsync(cancellationToken); - return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + await foreach (var token in collection.Find(token => + token.ApplicationId == ObjectId.Parse(identifier)).ToAsyncEnumerable(cancellationToken)) { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - - await foreach (var token in collection.Find(token => - token.ApplicationId == ObjectId.Parse(client) && - token.Subject == subject && - token.Status == status && - token.Type == type).ToAsyncEnumerable(cancellationToken)) - { - yield return token; - } + yield return token; } } + } - /// - public virtual IAsyncEnumerable FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) + /// + public virtual IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - return ExecuteAsync(cancellationToken); + return ExecuteAsync(cancellationToken); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - await foreach (var token in collection.Find(token => - token.ApplicationId == ObjectId.Parse(identifier)).ToAsyncEnumerable(cancellationToken)) - { - yield return token; - } + await foreach (var token in collection.Find(token => + token.AuthorizationId == ObjectId.Parse(identifier)).ToAsyncEnumerable(cancellationToken)) + { + yield return token; } } + } - /// - public virtual IAsyncEnumerable FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken) + /// + public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); + } - return ExecuteAsync(cancellationToken); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + return await collection.Find(token => token.Id == ObjectId.Parse(identifier)).FirstOrDefaultAsync(cancellationToken); + } - await foreach (var token in collection.Find(token => - token.AuthorizationId == ObjectId.Parse(identifier)).ToAsyncEnumerable(cancellationToken)) - { - yield return token; - } - } + /// + public virtual async ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); } - /// - public virtual async ValueTask FindByIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + return await collection.Find(token => token.ReferenceId == identifier).FirstOrDefaultAsync(cancellationToken); + } - return await collection.Find(token => token.Id == ObjectId.Parse(identifier)).FirstOrDefaultAsync(cancellationToken); + /// + public virtual IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); } - /// - public virtual async ValueTask FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier)); - } + return ExecuteAsync(cancellationToken); + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - return await collection.Find(token => token.ReferenceId == identifier).FirstOrDefaultAsync(cancellationToken); + await foreach (var token in collection.Find(token => token.Subject == subject).ToAsyncEnumerable(cancellationToken)) + { + yield return token; + } } + } - /// - public virtual IAsyncEnumerable FindBySubjectAsync(string subject, CancellationToken cancellationToken) + /// + public virtual ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (string.IsNullOrEmpty(subject)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); - } + throw new ArgumentNullException(nameof(token)); + } - return ExecuteAsync(cancellationToken); + if (token.ApplicationId == ObjectId.Empty) + { + return new ValueTask(result: null); + } - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + return new ValueTask(token.ApplicationId.ToString()); + } - await foreach (var token in collection.Find(token => token.Subject == subject).ToAsyncEnumerable(cancellationToken)) - { - yield return token; - } - } + /// + public virtual async ValueTask GetAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual ValueTask GetApplicationIdAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - if (token.ApplicationId == ObjectId.Empty) - { - return new ValueTask(result: null); - } + return await ((IMongoQueryable) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(cancellationToken); + } - return new ValueTask(token.ApplicationId.ToString()); + /// + public virtual ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask GetAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) + if (token.AuthorizationId == ObjectId.Empty) { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + return new ValueTask(result: null); + } - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + return new ValueTask(token.AuthorizationId.ToString()); + } - return await ((IMongoQueryable) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(cancellationToken); + /// + public virtual ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetAuthorizationIdAsync(TToken token, CancellationToken cancellationToken) + if (token.CreationDate is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(result: null); + } - if (token.AuthorizationId == ObjectId.Empty) - { - return new ValueTask(result: null); - } + return new ValueTask(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc)); + } - return new ValueTask(token.AuthorizationId.ToString()); + /// + public virtual ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetCreationDateAsync(TToken token, CancellationToken cancellationToken) + if (token.ExpirationDate is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(result: null); + } - if (token.CreationDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc)); + } - return new ValueTask(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetExpirationDateAsync(TToken token, CancellationToken cancellationToken) + return new ValueTask(token.Id.ToString()); + } + + /// + public virtual ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - if (token.ExpirationDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(token.Payload); + } - return new ValueTask(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask> GetPropertiesAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetIdAsync(TToken token, CancellationToken cancellationToken) + if (token.Properties is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - return new ValueTask(token.Id.ToString()); + return new ValueTask>(ImmutableDictionary.Create()); } - /// - public virtual ValueTask GetPayloadAsync(TToken token, CancellationToken cancellationToken) + using var document = JsonDocument.Parse(token.Properties.ToJson()); + var builder = ImmutableDictionary.CreateBuilder(); + + foreach (var property in document.RootElement.EnumerateObject()) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + builder[property.Name] = property.Value.Clone(); + } + + return new ValueTask>(builder.ToImmutable()); + } - return new ValueTask(token.Payload); + /// + public virtual ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask> GetPropertiesAsync(TToken token, CancellationToken cancellationToken) + if (token.RedemptionDate is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(result: null); + } - if (token.Properties is null) - { - return new ValueTask>(ImmutableDictionary.Create()); - } + return new ValueTask(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc)); + } - using var document = JsonDocument.Parse(token.Properties.ToJson()); - var builder = ImmutableDictionary.CreateBuilder(); + /// + public virtual ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - foreach (var property in document.RootElement.EnumerateObject()) - { - builder[property.Name] = property.Value.Clone(); - } + return new ValueTask(token.ReferenceId); + } - return new ValueTask>(builder.ToImmutable()); + /// + public virtual ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetRedemptionDateAsync(TToken token, CancellationToken cancellationToken) + return new ValueTask(token.Status); + } + + /// + public virtual ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - if (token.RedemptionDate is null) - { - return new ValueTask(result: null); - } + return new ValueTask(token.Subject); + } - return new ValueTask(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc)); + /// + public virtual ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask GetReferenceIdAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + return new ValueTask(token.Type); + } - return new ValueTask(token.ReferenceId); + /// + public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + { + try + { + return new ValueTask(Activator.CreateInstance()); } - /// - public virtual ValueTask GetStatusAsync(TToken token, CancellationToken cancellationToken) + catch (MemberAccessException exception) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - return new ValueTask(token.Status); + return new ValueTask(Task.FromException( + new InvalidOperationException(SR.GetResourceString(SR.ID0248), exception))); } + } - /// - public virtual ValueTask GetSubjectAsync(TToken token, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + /// + public virtual async IAsyncEnumerable ListAsync( + int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - return new ValueTask(token.Subject); - } + var query = (IMongoQueryable) collection.AsQueryable().OrderBy(token => token.Id); - /// - public virtual ValueTask GetTypeAsync(TToken token, CancellationToken cancellationToken) + if (offset.HasValue) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + query = query.Skip(offset.Value); + } - return new ValueTask(token.Type); + if (count.HasValue) + { + query = query.Take(count.Value); } - /// - public virtual ValueTask InstantiateAsync(CancellationToken cancellationToken) + await foreach (var token in ((IAsyncCursorSource) query).ToAsyncEnumerable(cancellationToken)) { - try - { - return new ValueTask(Activator.CreateInstance()); - } + yield return token; + } + } - catch (MemberAccessException exception) - { - return new ValueTask(Task.FromException( - new InvalidOperationException(SR.GetResourceString(SR.ID0248), exception))); - } + /// + public virtual IAsyncEnumerable ListAsync( + Func, TState, IQueryable> query, + TState state, CancellationToken cancellationToken) + { + if (query is null) + { + throw new ArgumentNullException(nameof(query)); } - /// - public virtual async IAsyncEnumerable ListAsync( - int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) + return ExecuteAsync(cancellationToken); + + async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - var query = (IMongoQueryable) collection.AsQueryable().OrderBy(token => token.Id); - - if (offset.HasValue) - { - query = query.Skip(offset.Value); - } - - if (count.HasValue) + await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable(cancellationToken)) { - query = query.Take(count.Value); - } - - await foreach (var token in ((IAsyncCursorSource) query).ToAsyncEnumerable(cancellationToken)) - { - yield return token; + yield return element; } } + } - /// - public virtual IAsyncEnumerable ListAsync( - Func, TState, IQueryable> query, - TState state, CancellationToken cancellationToken) - { - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + /// + public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + { + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - return ExecuteAsync(cancellationToken); + // Note: directly deleting the resulting set of an aggregate query is not supported by MongoDB. + // To work around this limitation, the token identifiers are stored in an intermediate list + // and delete requests are sent to remove the documents corresponding to these identifiers. - async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) - { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + var identifiers = + await (from token in collection.AsQueryable() + join authorization in database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName).AsQueryable() + on token.AuthorizationId equals authorization.Id into authorizations + where token.CreationDate < threshold.UtcDateTime + where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || + token.ExpirationDate < DateTime.UtcNow || + authorizations.Any(authorization => authorization.Status != Statuses.Valid) + select token.Id).ToListAsync(cancellationToken); - await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable(cancellationToken)) - { - yield return element; - } - } + // Note: to avoid generating delete requests with very large filters, a buffer is used here and the + // maximum number of elements that can be removed by a single call to PruneAsync() is deliberately limited. + foreach (var buffer in Buffer(identifiers.Take(1_000_000), 1_000)) + { + await collection.DeleteManyAsync(token => buffer.Contains(token.Id), cancellationToken); } - /// - public virtual async ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + static IEnumerable> Buffer(IEnumerable source, int count) { - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + List? buffer = null; - // Note: directly deleting the resulting set of an aggregate query is not supported by MongoDB. - // To work around this limitation, the token identifiers are stored in an intermediate list - // and delete requests are sent to remove the documents corresponding to these identifiers. - - var identifiers = - await (from token in collection.AsQueryable() - join authorization in database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName).AsQueryable() - on token.AuthorizationId equals authorization.Id into authorizations - where token.CreationDate < threshold.UtcDateTime - where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || - token.ExpirationDate < DateTime.UtcNow || - authorizations.Any(authorization => authorization.Status != Statuses.Valid) - select token.Id).ToListAsync(cancellationToken); - - // Note: to avoid generating delete requests with very large filters, a buffer is used here and the - // maximum number of elements that can be removed by a single call to PruneAsync() is deliberately limited. - foreach (var buffer in Buffer(identifiers.Take(1_000_000), 1_000)) + foreach (var element in source) { - await collection.DeleteManyAsync(token => buffer.Contains(token.Id), cancellationToken); - } + buffer ??= new List(capacity: 1); + buffer.Add(element); - static IEnumerable> Buffer(IEnumerable source, int count) - { - List? buffer = null; - - foreach (var element in source) - { - buffer ??= new List(capacity: 1); - buffer.Add(element); - - if (buffer.Count == count) - { - yield return buffer; - - buffer = null; - } - } - - if (buffer is not null) + if (buffer.Count == count) { yield return buffer; - } - } - } - - /// - public virtual ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - if (!string.IsNullOrEmpty(identifier)) - { - token.ApplicationId = ObjectId.Parse(identifier); + buffer = null; + } } - else + if (buffer is not null) { - token.ApplicationId = ObjectId.Empty; + yield return buffer; } - - return default; } + } - /// - public virtual ValueTask SetAuthorizationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + /// + public virtual ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - if (!string.IsNullOrEmpty(identifier)) - { - token.AuthorizationId = ObjectId.Parse(identifier); - } - - else - { - token.AuthorizationId = ObjectId.Empty; - } + throw new ArgumentNullException(nameof(token)); + } - return default; + if (!string.IsNullOrEmpty(identifier)) + { + token.ApplicationId = ObjectId.Parse(identifier); } - /// - public virtual ValueTask SetCreationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + else { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + token.ApplicationId = ObjectId.Empty; + } - token.CreationDate = date?.UtcDateTime; + return default; + } - return default; + /// + public virtual ValueTask SetAuthorizationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask SetExpirationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + if (!string.IsNullOrEmpty(identifier)) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - token.ExpirationDate = date?.UtcDateTime; - - return default; + token.AuthorizationId = ObjectId.Parse(identifier); } - /// - public virtual ValueTask SetPayloadAsync(TToken token, string? payload, CancellationToken cancellationToken) + else { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + token.AuthorizationId = ObjectId.Empty; + } - token.Payload = payload; + return default; + } - return default; + /// + public virtual ValueTask SetCreationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask SetPropertiesAsync(TToken token, - ImmutableDictionary properties, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + token.CreationDate = date?.UtcDateTime; - if (properties is null || properties.IsEmpty) - { - token.Properties = null; + return default; + } - return default; - } + /// + public virtual ValueTask SetExpirationDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + token.ExpirationDate = date?.UtcDateTime; - writer.WriteStartObject(); + return default; + } - foreach (var property in properties) - { - writer.WritePropertyName(property.Key); - property.Value.WriteTo(writer); - } + /// + public virtual ValueTask SetPayloadAsync(TToken token, string? payload, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } - writer.WriteEndObject(); - writer.Flush(); + token.Payload = payload; - token.Properties = BsonDocument.Parse(Encoding.UTF8.GetString(stream.ToArray())); + return default; + } - return default; + /// + public virtual ValueTask SetPropertiesAsync(TToken token, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask SetRedemptionDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + if (properties is null || properties.IsEmpty) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - token.RedemptionDate = date?.UtcDateTime; + token.Properties = null; return default; } - /// - public virtual ValueTask SetReferenceIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - token.ReferenceId = identifier; + writer.WriteStartObject(); - return default; + foreach (var property in properties) + { + writer.WritePropertyName(property.Key); + property.Value.WriteTo(writer); } - /// - public virtual ValueTask SetStatusAsync(TToken token, string? status, CancellationToken cancellationToken) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + writer.WriteEndObject(); + writer.Flush(); - token.Status = status; + token.Properties = BsonDocument.Parse(Encoding.UTF8.GetString(stream.ToArray())); - return default; + return default; + } + + /// + public virtual ValueTask SetRedemptionDateAsync(TToken token, DateTimeOffset? date, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask SetSubjectAsync(TToken token, string? subject, CancellationToken cancellationToken) + token.RedemptionDate = date?.UtcDateTime; + + return default; + } + + /// + public virtual ValueTask SetReferenceIdAsync(TToken token, string? identifier, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - token.Subject = subject; + token.ReferenceId = identifier; - return default; + return default; + } + + /// + public virtual ValueTask SetStatusAsync(TToken token, string? status, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual ValueTask SetTypeAsync(TToken token, string? type, CancellationToken cancellationToken) + token.Status = status; + + return default; + } + + /// + public virtual ValueTask SetSubjectAsync(TToken token, string? subject, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - token.Type = type; + token.Subject = subject; - return default; + return default; + } + + /// + public virtual ValueTask SetTypeAsync(TToken token, string? type, CancellationToken cancellationToken) + { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); } - /// - public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken) + token.Type = type; + + return default; + } + + /// + public virtual async ValueTask UpdateAsync(TToken token, CancellationToken cancellationToken) + { + if (token is null) { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } + throw new ArgumentNullException(nameof(token)); + } - // Generate a new concurrency token and attach it - // to the token before persisting the changes. - var timestamp = token.ConcurrencyToken; - token.ConcurrencyToken = Guid.NewGuid().ToString(); + // Generate a new concurrency token and attach it + // to the token before persisting the changes. + var timestamp = token.ConcurrencyToken; + token.ConcurrencyToken = Guid.NewGuid().ToString(); - var database = await Context.GetDatabaseAsync(cancellationToken); - var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - if ((await collection.ReplaceOneAsync(entity => - entity.Id == token.Id && - entity.ConcurrencyToken == timestamp, token, null as ReplaceOptions, cancellationToken)).MatchedCount == 0) - { - throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247)); - } + if ((await collection.ReplaceOneAsync(entity => + entity.Id == token.Id && + entity.ConcurrencyToken == timestamp, token, null as ReplaceOptions, cancellationToken)).MatchedCount == 0) + { + throw new OpenIddictExceptions.ConcurrencyException(SR.GetResourceString(SR.ID0247)); } } -} \ No newline at end of file +} diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzBuilder.cs b/src/OpenIddict.Quartz/OpenIddictQuartzBuilder.cs index f9e62c1e..67972b1d 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzBuilder.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzBuilder.cs @@ -9,113 +9,112 @@ using System.ComponentModel; using OpenIddict.Quartz; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the OpenIddict Quartz.NET integration. +/// +public class OpenIddictQuartzBuilder { /// - /// Exposes the necessary methods required to configure the OpenIddict Quartz.NET integration. + /// Initializes a new instance of . /// - public class OpenIddictQuartzBuilder + /// The services collection. + public OpenIddictQuartzBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict Quartz.NET configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictQuartzBuilder Configure(Action configuration) { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictQuartzBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Amends the default OpenIddict Quartz.NET configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictQuartzBuilder Configure(Action configuration) + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Disables authorizations pruning. - /// - /// The . - public OpenIddictQuartzBuilder DisableAuthorizationPruning() - => Configure(options => options.DisableAuthorizationPruning = true); - - /// - /// Disables tokens pruning. - /// - /// The . - public OpenIddictQuartzBuilder DisableTokenPruning() - => Configure(options => options.DisableTokenPruning = true); - - /// - /// Sets the number of times a failed Quartz.NET job can be retried. - /// - /// The number of times a failed Quartz.NET job can be retried. - /// The . - public OpenIddictQuartzBuilder SetMaximumRefireCount(int count) - { - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.GetResourceString(SR.ID0279)); - } + /// + /// Disables authorizations pruning. + /// + /// The . + public OpenIddictQuartzBuilder DisableAuthorizationPruning() + => Configure(options => options.DisableAuthorizationPruning = true); - return Configure(options => options.MaximumRefireCount = count); - } + /// + /// Disables tokens pruning. + /// + /// The . + public OpenIddictQuartzBuilder DisableTokenPruning() + => Configure(options => options.DisableTokenPruning = true); - /// - /// Sets the minimum lifespan authorizations must have to be pruned. - /// - /// The minimum lifespan authorizations must have to be pruned. - /// The . - public OpenIddictQuartzBuilder SetMinimumAuthorizationLifespan(TimeSpan lifespan) + /// + /// Sets the number of times a failed Quartz.NET job can be retried. + /// + /// The number of times a failed Quartz.NET job can be retried. + /// The . + public OpenIddictQuartzBuilder SetMaximumRefireCount(int count) + { + if (count < 0) { - if (lifespan < TimeSpan.FromMinutes(10)) - { - throw new ArgumentOutOfRangeException(nameof(lifespan), SR.GetResourceString(SR.ID0280)); - } - - return Configure(options => options.MinimumAuthorizationLifespan = lifespan); + throw new ArgumentOutOfRangeException(nameof(count), SR.GetResourceString(SR.ID0279)); } - /// - /// Sets the minimum lifespan tokens must have to be pruned. - /// - /// The minimum lifespan tokens must have to be pruned. - /// The . - public OpenIddictQuartzBuilder SetMinimumTokenLifespan(TimeSpan lifespan) - { - if (lifespan < TimeSpan.FromMinutes(10)) - { - throw new ArgumentOutOfRangeException(nameof(lifespan), SR.GetResourceString(SR.ID0280)); - } + return Configure(options => options.MaximumRefireCount = count); + } - return Configure(options => options.MinimumTokenLifespan = lifespan); + /// + /// Sets the minimum lifespan authorizations must have to be pruned. + /// + /// The minimum lifespan authorizations must have to be pruned. + /// The . + public OpenIddictQuartzBuilder SetMinimumAuthorizationLifespan(TimeSpan lifespan) + { + if (lifespan < TimeSpan.FromMinutes(10)) + { + throw new ArgumentOutOfRangeException(nameof(lifespan), SR.GetResourceString(SR.ID0280)); } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + return Configure(options => options.MinimumAuthorizationLifespan = lifespan); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + /// Sets the minimum lifespan tokens must have to be pruned. + /// + /// The minimum lifespan tokens must have to be pruned. + /// The . + public OpenIddictQuartzBuilder SetMinimumTokenLifespan(TimeSpan lifespan) + { + if (lifespan < TimeSpan.FromMinutes(10)) + { + throw new ArgumentOutOfRangeException(nameof(lifespan), SR.GetResourceString(SR.ID0280)); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); + return Configure(options => options.MinimumTokenLifespan = lifespan); } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs b/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs index 8567b71f..b28baf81 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzConfiguration.cs @@ -9,38 +9,37 @@ using Microsoft.Extensions.Options; using Quartz; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Quartz +namespace OpenIddict.Quartz; + +/// +/// Contains the methods required to ensure that the OpenIddict Quartz.NET configuration is valid. +/// +public class OpenIddictQuartzConfiguration : IConfigureOptions { - /// - /// Contains the methods required to ensure that the OpenIddict Quartz.NET configuration is valid. - /// - public class OpenIddictQuartzConfiguration : IConfigureOptions + /// + public void Configure(QuartzOptions options) { - /// - public void Configure(QuartzOptions options) + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new ArgumentNullException(nameof(options)); + } - options.AddJob(builder => - { - builder.StoreDurably() - .WithIdentity(OpenIddictQuartzJob.Identity) - .WithDescription(SR.GetResourceString(SR.ID8001)); - }); + options.AddJob(builder => + { + builder.StoreDurably() + .WithIdentity(OpenIddictQuartzJob.Identity) + .WithDescription(SR.GetResourceString(SR.ID8001)); + }); - options.AddTrigger(builder => - { - // Note: this trigger uses a quite long interval (1 hour), which means it may be potentially never - // reached if the application is shut down or recycled. As such, this trigger is set up to fire - // between 1 and 10 minutes after the application starts to ensure the job is executed at least once. - builder.ForJob(OpenIddictQuartzJob.Identity) - .WithSimpleSchedule(options => options.WithIntervalInHours(1).RepeatForever()) - .WithDescription(SR.GetResourceString(SR.ID8002)) - .StartAt(DateBuilder.FutureDate(new Random().Next(1, 10), IntervalUnit.Minute)); - }); - } + options.AddTrigger(builder => + { + // Note: this trigger uses a quite long interval (1 hour), which means it may be potentially never + // reached if the application is shut down or recycled. As such, this trigger is set up to fire + // between 1 and 10 minutes after the application starts to ensure the job is executed at least once. + builder.ForJob(OpenIddictQuartzJob.Identity) + .WithSimpleSchedule(options => options.WithIntervalInHours(1).RepeatForever()) + .WithDescription(SR.GetResourceString(SR.ID8002)) + .StartAt(DateBuilder.FutureDate(new Random().Next(1, 10), IntervalUnit.Minute)); + }); } } diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs b/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs index d2d3d501..f414aa9a 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzExtensions.cs @@ -10,62 +10,61 @@ using Microsoft.Extensions.Options; using OpenIddict.Quartz; using Quartz; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict Quartz.NET integration. +/// +public static class OpenIddictQuartzExtensions { /// - /// Exposes extensions allowing to register the OpenIddict Quartz.NET integration. + /// Registers the OpenIddict Quartz.NET integration in the DI container. /// - public static class OpenIddictQuartzExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictQuartzBuilder UseQuartz(this OpenIddictCoreBuilder builder) { - /// - /// Registers the OpenIddict Quartz.NET integration in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictQuartzBuilder UseQuartz(this OpenIddictCoreBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddQuartz(); + builder.Services.AddQuartz(); - // The OpenIddict job is registered as a service to allow - // Quartz.NET's DI integration to resolve it from the DI. - builder.Services.TryAddTransient(); + // The OpenIddict job is registered as a service to allow + // Quartz.NET's DI integration to resolve it from the DI. + builder.Services.TryAddTransient(); - // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< - IConfigureOptions, OpenIddictQuartzConfiguration>()); + // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< + IConfigureOptions, OpenIddictQuartzConfiguration>()); - return new OpenIddictQuartzBuilder(builder.Services); - } + return new OpenIddictQuartzBuilder(builder.Services); + } - /// - /// Registers the OpenIddict Quartz.NET integration in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the Quartz.NET services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictCoreBuilder UseQuartz( - this OpenIddictCoreBuilder builder, Action configuration) + /// + /// Registers the OpenIddict Quartz.NET integration in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the Quartz.NET services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictCoreBuilder UseQuartz( + this OpenIddictCoreBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseQuartz()); + configuration(builder.UseQuartz()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs b/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs index c29f7716..35cc68f2 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzJob.cs @@ -13,197 +13,196 @@ using OpenIddict.Abstractions; using Quartz; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Quartz +namespace OpenIddict.Quartz; + +/// +/// Represents a Quartz.NET job performing scheduled tasks for OpenIddict. +/// +[DisallowConcurrentExecution] +public class OpenIddictQuartzJob : IJob { + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + /// + /// Creates a new instance of the class. + /// + public OpenIddictQuartzJob() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0082)); + /// - /// Represents a Quartz.NET job performing scheduled tasks for OpenIddict. + /// Creates a new instance of the class. /// - [DisallowConcurrentExecution] - public class OpenIddictQuartzJob : IJob + /// The OpenIddict Quartz.NET options. + /// The service provider. + public OpenIddictQuartzJob(IOptionsMonitor options, IServiceProvider provider) { - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - /// - /// Creates a new instance of the class. - /// - public OpenIddictQuartzJob() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0082)); - - /// - /// Creates a new instance of the class. - /// - /// The OpenIddict Quartz.NET options. - /// The service provider. - public OpenIddictQuartzJob(IOptionsMonitor options, IServiceProvider provider) + _options = options; + _provider = provider; + } + + /// + /// Gets the default identity assigned to this job. + /// + public static JobKey Identity { get; } = new JobKey( + name: typeof(OpenIddictQuartzJob).Name, + group: typeof(OpenIddictQuartzJob).Assembly.GetName().Name!); + + /// + public async Task Execute(IJobExecutionContext context) + { + if (context is null) { - _options = options; - _provider = provider; + throw new ArgumentNullException(nameof(context)); } - /// - /// Gets the default identity assigned to this job. - /// - public static JobKey Identity { get; } = new JobKey( - name: typeof(OpenIddictQuartzJob).Name, - group: typeof(OpenIddictQuartzJob).Assembly.GetName().Name!); + List? exceptions = null; + + // Note: this job is registered as a transient service. As such, it cannot directly depend on scoped services + // like the core managers. To work around this limitation, a scope is manually created for each invocation. + var scope = _provider.CreateScope(); - /// - public async Task Execute(IJobExecutionContext context) + try { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + // Note: this background task is responsible of automatically removing orphaned tokens/authorizations + // (i.e tokens that are no longer valid and ad-hoc authorizations that have no valid tokens associated). + // Import: since tokens associated to ad-hoc authorizations are not removed as part of the same operation, + // the tokens MUST be deleted before removing the ad-hoc authorizations that no longer have any token. - List? exceptions = null; + if (!_options.CurrentValue.DisableTokenPruning) + { + var manager = scope.ServiceProvider.GetService(); + if (manager is null) + { + // Inform Quartz.NET that the triggers associated with this job should be removed, + // as the future invocations will always fail until the application is correctly + // re-configured to register the OpenIddict core services in the DI container. + throw new JobExecutionException(new InvalidOperationException(SR.GetResourceString(SR.ID0278))) + { + RefireImmediately = false, + UnscheduleAllTriggers = true, + UnscheduleFiringTrigger = true + }; + } - // Note: this job is registered as a transient service. As such, it cannot directly depend on scoped services - // like the core managers. To work around this limitation, a scope is manually created for each invocation. - var scope = _provider.CreateScope(); + var threshold = DateTimeOffset.UtcNow - _options.CurrentValue.MinimumTokenLifespan; - try - { - // Note: this background task is responsible of automatically removing orphaned tokens/authorizations - // (i.e tokens that are no longer valid and ad-hoc authorizations that have no valid tokens associated). - // Import: since tokens associated to ad-hoc authorizations are not removed as part of the same operation, - // the tokens MUST be deleted before removing the ad-hoc authorizations that no longer have any token. + try + { + await manager.PruneAsync(threshold, context.CancellationToken); + } - if (!_options.CurrentValue.DisableTokenPruning) + // OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is. + catch (OutOfMemoryException) { - var manager = scope.ServiceProvider.GetService(); - if (manager is null) - { - // Inform Quartz.NET that the triggers associated with this job should be removed, - // as the future invocations will always fail until the application is correctly - // re-configured to register the OpenIddict core services in the DI container. - throw new JobExecutionException(new InvalidOperationException(SR.GetResourceString(SR.ID0278))) - { - RefireImmediately = false, - UnscheduleAllTriggers = true, - UnscheduleFiringTrigger = true - }; - } - - var threshold = DateTimeOffset.UtcNow - _options.CurrentValue.MinimumTokenLifespan; - - try - { - await manager.PruneAsync(threshold, context.CancellationToken); - } + throw; + } - // OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is. - catch (OutOfMemoryException) + // OperationCanceledExceptions are typically thrown when the host is about to shut down. + // To allow the host to shut down as fast as possible, this exception type is special-cased + // to prevent further processing in this job and inform Quartz.NET it shouldn't be refired. + catch (OperationCanceledException exception) when (exception.CancellationToken == context.CancellationToken) + { + throw new JobExecutionException(exception) { - throw; - } + RefireImmediately = false + }; + } - // OperationCanceledExceptions are typically thrown when the host is about to shut down. - // To allow the host to shut down as fast as possible, this exception type is special-cased - // to prevent further processing in this job and inform Quartz.NET it shouldn't be refired. - catch (OperationCanceledException exception) when (exception.CancellationToken == context.CancellationToken) - { - throw new JobExecutionException(exception) - { - RefireImmediately = false - }; - } - - // AggregateExceptions are generally thrown by the manager itself when one or multiple exception(s) - // occurred while trying to prune the entities. In this case, add the inner exceptions to the collection. - catch (AggregateException exception) - { - exceptions ??= new List(capacity: exception.InnerExceptions.Count); - exceptions.AddRange(exception.InnerExceptions); - } + // AggregateExceptions are generally thrown by the manager itself when one or multiple exception(s) + // occurred while trying to prune the entities. In this case, add the inner exceptions to the collection. + catch (AggregateException exception) + { + exceptions ??= new List(capacity: exception.InnerExceptions.Count); + exceptions.AddRange(exception.InnerExceptions); + } - // Other exceptions are assumed to be transient and are added to the exceptions collection - // to be re-thrown later (typically, at the very end of this job, as an AggregateException). - catch (Exception exception) - { - exceptions ??= new List(capacity: 1); - exceptions.Add(exception); - } + // Other exceptions are assumed to be transient and are added to the exceptions collection + // to be re-thrown later (typically, at the very end of this job, as an AggregateException). + catch (Exception exception) + { + exceptions ??= new List(capacity: 1); + exceptions.Add(exception); } + } - if (!_options.CurrentValue.DisableAuthorizationPruning) + if (!_options.CurrentValue.DisableAuthorizationPruning) + { + var manager = scope.ServiceProvider.GetService(); + if (manager is null) { - var manager = scope.ServiceProvider.GetService(); - if (manager is null) + // Inform Quartz.NET that the triggers associated with this job should be removed, + // as the future invocations will always fail until the application is correctly + // re-configured to register the OpenIddict core services in the DI container. + throw new JobExecutionException(new InvalidOperationException(SR.GetResourceString(SR.ID0278))) { - // Inform Quartz.NET that the triggers associated with this job should be removed, - // as the future invocations will always fail until the application is correctly - // re-configured to register the OpenIddict core services in the DI container. - throw new JobExecutionException(new InvalidOperationException(SR.GetResourceString(SR.ID0278))) - { - RefireImmediately = false, - UnscheduleAllTriggers = true, - UnscheduleFiringTrigger = true - }; - } - - var threshold = DateTimeOffset.UtcNow - _options.CurrentValue.MinimumAuthorizationLifespan; - - try - { - await manager.PruneAsync(threshold, context.CancellationToken); - } + RefireImmediately = false, + UnscheduleAllTriggers = true, + UnscheduleFiringTrigger = true + }; + } - // OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is. - catch (OutOfMemoryException) - { - throw; - } + var threshold = DateTimeOffset.UtcNow - _options.CurrentValue.MinimumAuthorizationLifespan; - // OperationCanceledExceptions are typically thrown when the host is about to shut down. - // To allow the host to shut down as fast as possible, this exception type is special-cased - // to prevent further processing in this job and inform Quartz.NET it shouldn't be refired. - catch (OperationCanceledException exception) when (exception.CancellationToken == context.CancellationToken) - { - throw new JobExecutionException(exception) - { - RefireImmediately = false - }; - } - - // AggregateExceptions are generally thrown by the manager itself when one or multiple exception(s) - // occurred while trying to prune the entities. In this case, add the inner exceptions to the collection. - catch (AggregateException exception) - { - exceptions ??= new List(capacity: exception.InnerExceptions.Count); - exceptions.AddRange(exception.InnerExceptions); - } + try + { + await manager.PruneAsync(threshold, context.CancellationToken); + } - // Other exceptions are assumed to be transient and are added to the exceptions collection - // to be re-thrown later (typically, at the very end of this job, as an AggregateException). - catch (Exception exception) - { - exceptions ??= new List(capacity: 1); - exceptions.Add(exception); - } + // OutOfMemoryExceptions are treated as fatal errors and are always re-thrown as-is. + catch (OutOfMemoryException) + { + throw; } - if (exceptions is not null) + // OperationCanceledExceptions are typically thrown when the host is about to shut down. + // To allow the host to shut down as fast as possible, this exception type is special-cased + // to prevent further processing in this job and inform Quartz.NET it shouldn't be refired. + catch (OperationCanceledException exception) when (exception.CancellationToken == context.CancellationToken) { - throw new JobExecutionException(new AggregateException(exceptions)) + throw new JobExecutionException(exception) { - // Only refire the job if the maximum refire count set in the options wasn't reached. - RefireImmediately = context.RefireCount < _options.CurrentValue.MaximumRefireCount + RefireImmediately = false }; } - } - finally - { - if (scope is IAsyncDisposable disposable) + // AggregateExceptions are generally thrown by the manager itself when one or multiple exception(s) + // occurred while trying to prune the entities. In this case, add the inner exceptions to the collection. + catch (AggregateException exception) { - await disposable.DisposeAsync(); + exceptions ??= new List(capacity: exception.InnerExceptions.Count); + exceptions.AddRange(exception.InnerExceptions); } - else + // Other exceptions are assumed to be transient and are added to the exceptions collection + // to be re-thrown later (typically, at the very end of this job, as an AggregateException). + catch (Exception exception) { - scope.Dispose(); + exceptions ??= new List(capacity: 1); + exceptions.Add(exception); } } + + if (exceptions is not null) + { + throw new JobExecutionException(new AggregateException(exceptions)) + { + // Only refire the job if the maximum refire count set in the options wasn't reached. + RefireImmediately = context.RefireCount < _options.CurrentValue.MaximumRefireCount + }; + } + } + + finally + { + if (scope is IAsyncDisposable disposable) + { + await disposable.DisposeAsync(); + } + + else + { + scope.Dispose(); + } } } } diff --git a/src/OpenIddict.Quartz/OpenIddictQuartzOptions.cs b/src/OpenIddict.Quartz/OpenIddictQuartzOptions.cs index 4cadf040..4d93810d 100644 --- a/src/OpenIddict.Quartz/OpenIddictQuartzOptions.cs +++ b/src/OpenIddict.Quartz/OpenIddictQuartzOptions.cs @@ -6,39 +6,38 @@ using System; -namespace OpenIddict.Quartz +namespace OpenIddict.Quartz; + +/// +/// Provides various settings needed to configure the OpenIddict Quartz.NET integration. +/// +public class OpenIddictQuartzOptions { /// - /// Provides various settings needed to configure the OpenIddict Quartz.NET integration. + /// Gets or sets a boolean indicating whether authorizations pruning should be disabled. /// - public class OpenIddictQuartzOptions - { - /// - /// Gets or sets a boolean indicating whether authorizations pruning should be disabled. - /// - public bool DisableAuthorizationPruning { get; set; } + public bool DisableAuthorizationPruning { get; set; } - /// - /// Gets or sets a boolean indicating whether tokens pruning should be disabled. - /// - public bool DisableTokenPruning { get; set; } + /// + /// Gets or sets a boolean indicating whether tokens pruning should be disabled. + /// + public bool DisableTokenPruning { get; set; } - /// - /// Gets or sets the number of times a failed Quartz.NET job can be retried. - /// By default, failed jobs are automatically retried twice after the initial failure. - /// - public int MaximumRefireCount { get; set; } = 2; + /// + /// Gets or sets the number of times a failed Quartz.NET job can be retried. + /// By default, failed jobs are automatically retried twice after the initial failure. + /// + public int MaximumRefireCount { get; set; } = 2; - /// - /// Gets or sets the minimum lifespan authorizations must have to be pruned. - /// By default, this value is set to 14 days and cannot be less than 10 minutes. - /// - public TimeSpan MinimumAuthorizationLifespan { get; set; } = TimeSpan.FromDays(14); + /// + /// Gets or sets the minimum lifespan authorizations must have to be pruned. + /// By default, this value is set to 14 days and cannot be less than 10 minutes. + /// + public TimeSpan MinimumAuthorizationLifespan { get; set; } = TimeSpan.FromDays(14); - /// - /// Gets or sets the minimum lifespan tokens must have to be pruned. - /// By default, this value is set to 14 days and cannot be less than 10 minutes. - /// - public TimeSpan MinimumTokenLifespan { get; set; } = TimeSpan.FromDays(14); - } + /// + /// Gets or sets the minimum lifespan tokens must have to be pruned. + /// By default, this value is set to 14 days and cannot be less than 10 minutes. + /// + public TimeSpan MinimumTokenLifespan { get; set; } = TimeSpan.FromDays(14); } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreBuilder.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreBuilder.cs index 038cc08d..cc9ecf2a 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreBuilder.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreBuilder.cs @@ -12,200 +12,199 @@ using Microsoft.Extensions.Caching.Distributed; using OpenIddict.Server.AspNetCore; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure +/// the OpenIddict server ASP.NET Core integration. +/// +public class OpenIddictServerAspNetCoreBuilder { /// - /// Exposes the necessary methods required to configure - /// the OpenIddict server ASP.NET Core integration. + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictServerAspNetCoreBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. /// - public class OpenIddictServerAspNetCoreBuilder + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict server ASP.NET Core configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictServerAspNetCoreBuilder Configure(Action configuration) { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictServerAspNetCoreBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Amends the default OpenIddict server ASP.NET Core configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictServerAspNetCoreBuilder Configure(Action configuration) + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Disables the transport security requirement (HTTPS). - /// - /// The . - public OpenIddictServerAspNetCoreBuilder DisableTransportSecurityRequirement() - => Configure(options => options.DisableTransportSecurityRequirement = true); - - /// - /// Enables the pass-through mode for the OpenID Connect authorization endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerAspNetCoreBuilder EnableAuthorizationEndpointPassthrough() - => Configure(options => options.EnableAuthorizationEndpointPassthrough = true); - - /// - /// Enables error pass-through support, so that the rest of the request processing pipeline is - /// automatically invoked when returning an error from the interactive authorization and logout endpoints. - /// When this option is enabled, special logic must be added to these actions to handle errors, that can be - /// retrieved using . - /// - /// - /// Important: the error pass-through mode cannot be used when the status code pages integration is enabled. - /// - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerAspNetCoreBuilder EnableErrorPassthrough() - => Configure(options => options.EnableErrorPassthrough = true); - - /// - /// Enables the pass-through mode for the OpenID Connect logout endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerAspNetCoreBuilder EnableLogoutEndpointPassthrough() - => Configure(options => options.EnableLogoutEndpointPassthrough = true); - - /// - /// Enables the pass-through mode for the OpenID Connect token endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerAspNetCoreBuilder EnableTokenEndpointPassthrough() - => Configure(options => options.EnableTokenEndpointPassthrough = true); - - /// - /// Enables the pass-through mode for the OpenID Connect userinfo endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerAspNetCoreBuilder EnableUserinfoEndpointPassthrough() - => Configure(options => options.EnableUserinfoEndpointPassthrough = true); - - /// - /// Enables the pass-through mode for the OpenID Connect user verification endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerAspNetCoreBuilder EnableVerificationEndpointPassthrough() - => Configure(options => options.EnableVerificationEndpointPassthrough = true); - - /// - /// Enables authorization request caching, so that authorization requests - /// are automatically stored in the distributed cache, which allows flowing - /// large payloads across requests. Enabling this option is recommended - /// when using external authentication providers or when large GET or POST - /// OpenID Connect authorization requests support is required. - /// - /// The . - public OpenIddictServerAspNetCoreBuilder EnableAuthorizationRequestCaching() - => Configure(options => options.EnableAuthorizationRequestCaching = true); - - /// - /// Enables logout request caching, so that logout requests - /// are automatically stored in the distributed cache. - /// - /// The . - public OpenIddictServerAspNetCoreBuilder EnableLogoutRequestCaching() - => Configure(options => options.EnableLogoutRequestCaching = true); - - /// - /// Enables status code pages integration support. Once enabled, errors - /// generated by the interactive endpoints can be handled by ASP.NET Core. - /// - /// The . - public OpenIddictServerAspNetCoreBuilder EnableStatusCodePagesIntegration() - => Configure(options => options.EnableStatusCodePagesIntegration = true); - - /// - /// Sets the realm returned to the caller as part of the WWW-Authenticate header. - /// - /// The issuer address. - /// The . - public OpenIddictServerAspNetCoreBuilder SetRealm(string realm) - { - if (string.IsNullOrEmpty(realm)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0107), nameof(realm)); - } + /// + /// Disables the transport security requirement (HTTPS). + /// + /// The . + public OpenIddictServerAspNetCoreBuilder DisableTransportSecurityRequirement() + => Configure(options => options.DisableTransportSecurityRequirement = true); - return Configure(options => options.Realm = realm); - } + /// + /// Enables the pass-through mode for the OpenID Connect authorization endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerAspNetCoreBuilder EnableAuthorizationEndpointPassthrough() + => Configure(options => options.EnableAuthorizationEndpointPassthrough = true); - /// - /// Sets the caching policy used by the authorization endpoint. - /// Note: the specified policy is only used when caching is explicitly enabled. - /// - /// The caching policy. - /// The . - public OpenIddictServerAspNetCoreBuilder SetAuthorizationRequestCachingPolicy(DistributedCacheEntryOptions policy) - { - if (policy is null) - { - throw new ArgumentNullException(nameof(policy)); - } + /// + /// Enables error pass-through support, so that the rest of the request processing pipeline is + /// automatically invoked when returning an error from the interactive authorization and logout endpoints. + /// When this option is enabled, special logic must be added to these actions to handle errors, that can be + /// retrieved using . + /// + /// + /// Important: the error pass-through mode cannot be used when the status code pages integration is enabled. + /// + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictServerAspNetCoreBuilder EnableErrorPassthrough() + => Configure(options => options.EnableErrorPassthrough = true); - return Configure(options => options.AuthorizationRequestCachingPolicy = policy); - } + /// + /// Enables the pass-through mode for the OpenID Connect logout endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerAspNetCoreBuilder EnableLogoutEndpointPassthrough() + => Configure(options => options.EnableLogoutEndpointPassthrough = true); - /// - /// Sets the caching policy used by the logout endpoint. - /// Note: the specified policy is only used when caching is explicitly enabled. - /// - /// The caching policy. - /// The . - public OpenIddictServerAspNetCoreBuilder SetLogoutRequestCachingPolicy(DistributedCacheEntryOptions policy) + /// + /// Enables the pass-through mode for the OpenID Connect token endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerAspNetCoreBuilder EnableTokenEndpointPassthrough() + => Configure(options => options.EnableTokenEndpointPassthrough = true); + + /// + /// Enables the pass-through mode for the OpenID Connect userinfo endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerAspNetCoreBuilder EnableUserinfoEndpointPassthrough() + => Configure(options => options.EnableUserinfoEndpointPassthrough = true); + + /// + /// Enables the pass-through mode for the OpenID Connect user verification endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerAspNetCoreBuilder EnableVerificationEndpointPassthrough() + => Configure(options => options.EnableVerificationEndpointPassthrough = true); + + /// + /// Enables authorization request caching, so that authorization requests + /// are automatically stored in the distributed cache, which allows flowing + /// large payloads across requests. Enabling this option is recommended + /// when using external authentication providers or when large GET or POST + /// OpenID Connect authorization requests support is required. + /// + /// The . + public OpenIddictServerAspNetCoreBuilder EnableAuthorizationRequestCaching() + => Configure(options => options.EnableAuthorizationRequestCaching = true); + + /// + /// Enables logout request caching, so that logout requests + /// are automatically stored in the distributed cache. + /// + /// The . + public OpenIddictServerAspNetCoreBuilder EnableLogoutRequestCaching() + => Configure(options => options.EnableLogoutRequestCaching = true); + + /// + /// Enables status code pages integration support. Once enabled, errors + /// generated by the interactive endpoints can be handled by ASP.NET Core. + /// + /// The . + public OpenIddictServerAspNetCoreBuilder EnableStatusCodePagesIntegration() + => Configure(options => options.EnableStatusCodePagesIntegration = true); + + /// + /// Sets the realm returned to the caller as part of the WWW-Authenticate header. + /// + /// The issuer address. + /// The . + public OpenIddictServerAspNetCoreBuilder SetRealm(string realm) + { + if (string.IsNullOrEmpty(realm)) { - if (policy is null) - { - throw new ArgumentNullException(nameof(policy)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0107), nameof(realm)); + } - return Configure(options => options.LogoutRequestCachingPolicy = policy); + return Configure(options => options.Realm = realm); + } + + /// + /// Sets the caching policy used by the authorization endpoint. + /// Note: the specified policy is only used when caching is explicitly enabled. + /// + /// The caching policy. + /// The . + public OpenIddictServerAspNetCoreBuilder SetAuthorizationRequestCachingPolicy(DistributedCacheEntryOptions policy) + { + if (policy is null) + { + throw new ArgumentNullException(nameof(policy)); } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + return Configure(options => options.AuthorizationRequestCachingPolicy = policy); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + /// Sets the caching policy used by the logout endpoint. + /// Note: the specified policy is only used when caching is explicitly enabled. + /// + /// The caching policy. + /// The . + public OpenIddictServerAspNetCoreBuilder SetLogoutRequestCachingPolicy(DistributedCacheEntryOptions policy) + { + if (policy is null) + { + throw new ArgumentNullException(nameof(policy)); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); + return Configure(options => options.LogoutRequestCachingPolicy = policy); } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs index 72aaf11e..a41ee087 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs @@ -10,100 +10,99 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +/// +/// Contains the methods required to ensure that the OpenIddict server configuration is valid. +/// +public class OpenIddictServerAspNetCoreConfiguration : IConfigureOptions, + IConfigureOptions, + IPostConfigureOptions, + IPostConfigureOptions { /// - /// Contains the methods required to ensure that the OpenIddict server configuration is valid. + /// Registers the OpenIddict server handler in the global authentication options. /// - public class OpenIddictServerAspNetCoreConfiguration : IConfigureOptions, - IConfigureOptions, - IPostConfigureOptions, - IPostConfigureOptions + /// The options instance to initialize. + public void Configure(AuthenticationOptions options) { - /// - /// Registers the OpenIddict server handler in the global authentication options. - /// - /// The options instance to initialize. - public void Configure(AuthenticationOptions options) + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - // If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception. - if (options.SchemeMap.TryGetValue(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, out var builder) && - builder.HandlerType != typeof(OpenIddictServerAspNetCoreHandler)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0108)); - } + throw new ArgumentNullException(nameof(options)); + } - options.AddScheme( - OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, displayName: null); + // If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception. + if (options.SchemeMap.TryGetValue(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, out var builder) && + builder.HandlerType != typeof(OpenIddictServerAspNetCoreHandler)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0108)); } - public void Configure(OpenIddictServerOptions options) + options.AddScheme( + OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, displayName: null); + } + + public void Configure(OpenIddictServerOptions options) + { + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new ArgumentNullException(nameof(options)); + } - // Register the built-in event handlers used by the OpenIddict ASP.NET Core server components. - options.Handlers.AddRange(OpenIddictServerAspNetCoreHandlers.DefaultHandlers); + // Register the built-in event handlers used by the OpenIddict ASP.NET Core server components. + options.Handlers.AddRange(OpenIddictServerAspNetCoreHandlers.DefaultHandlers); + } + + /// + /// Ensures that the authentication configuration is in a consistent and valid state. + /// + /// The name of the options instance to configure, if applicable. + /// The options instance to initialize. + public void PostConfigure(string name, AuthenticationOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); } - /// - /// Ensures that the authentication configuration is in a consistent and valid state. - /// - /// The name of the options instance to configure, if applicable. - /// The options instance to initialize. - public void PostConfigure(string name, AuthenticationOptions options) + if (!TryValidate(options.SchemeMap, options.DefaultAuthenticateScheme) || + !TryValidate(options.SchemeMap, options.DefaultChallengeScheme) || + !TryValidate(options.SchemeMap, options.DefaultForbidScheme) || + !TryValidate(options.SchemeMap, options.DefaultScheme) || + !TryValidate(options.SchemeMap, options.DefaultSignInScheme) || + !TryValidate(options.SchemeMap, options.DefaultSignOutScheme)) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0109)); + } - if (!TryValidate(options.SchemeMap, options.DefaultAuthenticateScheme) || - !TryValidate(options.SchemeMap, options.DefaultChallengeScheme) || - !TryValidate(options.SchemeMap, options.DefaultForbidScheme) || - !TryValidate(options.SchemeMap, options.DefaultScheme) || - !TryValidate(options.SchemeMap, options.DefaultSignInScheme) || - !TryValidate(options.SchemeMap, options.DefaultSignOutScheme)) + static bool TryValidate(IDictionary map, string? scheme) + { + // If the scheme was not set or if it cannot be found in the map, return true. + if (string.IsNullOrEmpty(scheme) || !map.TryGetValue(scheme, out var builder)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0109)); + return true; } - static bool TryValidate(IDictionary map, string? scheme) - { - // If the scheme was not set or if it cannot be found in the map, return true. - if (string.IsNullOrEmpty(scheme) || !map.TryGetValue(scheme, out var builder)) - { - return true; - } - - return builder.HandlerType != typeof(OpenIddictServerAspNetCoreHandler); - } + return builder.HandlerType != typeof(OpenIddictServerAspNetCoreHandler); } + } - /// - /// Populates the default OpenIddict server ASP.NET Core options and - /// ensures that the configuration is in a consistent and valid state. - /// - /// The name of the options instance to configure, if applicable. - /// The options instance to initialize. - public void PostConfigure(string name, OpenIddictServerAspNetCoreOptions options) + /// + /// Populates the default OpenIddict server ASP.NET Core options and + /// ensures that the configuration is in a consistent and valid state. + /// + /// The name of the options instance to configure, if applicable. + /// The options instance to initialize. + public void PostConfigure(string name, OpenIddictServerAspNetCoreOptions options) + { + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new ArgumentNullException(nameof(options)); + } - if (options.EnableErrorPassthrough && options.EnableStatusCodePagesIntegration) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0110)); - } + if (options.EnableErrorPassthrough && options.EnableStatusCodePagesIntegration) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0110)); } } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs index 227e96cc..376d79f1 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs @@ -4,40 +4,39 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +/// +/// Exposes common constants used by the OpenIddict ASP.NET Core host. +/// +public static class OpenIddictServerAspNetCoreConstants { - /// - /// Exposes common constants used by the OpenIddict ASP.NET Core host. - /// - public static class OpenIddictServerAspNetCoreConstants + public static class Cache { - public static class Cache - { - public const string AuthorizationRequest = "openiddict-authorization-request:"; - public const string LogoutRequest = "openiddict-logout-request:"; - } + public const string AuthorizationRequest = "openiddict-authorization-request:"; + public const string LogoutRequest = "openiddict-logout-request:"; + } - public static class JsonWebTokenTypes + public static class JsonWebTokenTypes + { + public static class Private { - public static class Private - { - public const string AuthorizationRequest = "oi_authrq+jwt"; - public const string LogoutRequest = "oi_lgtrq+jwt"; - } + public const string AuthorizationRequest = "oi_authrq+jwt"; + public const string LogoutRequest = "oi_lgtrq+jwt"; } + } - public static class Properties - { - public const string AccessTokenPrincipal = ".access_token_principal"; - public const string AuthorizationCodePrincipal = ".authorization_code_principal"; - public const string DeviceCodePrincipal = ".device_code_principal"; - public const string Error = ".error"; - public const string ErrorDescription = ".error_description"; - public const string ErrorUri = ".error_uri"; - public const string IdentityTokenPrincipal = ".identity_token_principal"; - public const string RefreshTokenPrincipal = ".refresh_token_principal"; - public const string Scope = ".scope"; - public const string UserCodePrincipal = ".user_code_principal"; - } + public static class Properties + { + public const string AccessTokenPrincipal = ".access_token_principal"; + public const string AuthorizationCodePrincipal = ".authorization_code_principal"; + public const string DeviceCodePrincipal = ".device_code_principal"; + public const string Error = ".error"; + public const string ErrorDescription = ".error_description"; + public const string ErrorUri = ".error_uri"; + public const string IdentityTokenPrincipal = ".identity_token_principal"; + public const string RefreshTokenPrincipal = ".refresh_token_principal"; + public const string Scope = ".scope"; + public const string UserCodePrincipal = ".user_code_principal"; } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreDefaults.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreDefaults.cs index 19f17b6c..22488fe9 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreDefaults.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreDefaults.cs @@ -6,16 +6,15 @@ using Microsoft.AspNetCore.Authentication; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +/// +/// Exposes the default values used by the OpenIddict server handler. +/// +public static class OpenIddictServerAspNetCoreDefaults { /// - /// Exposes the default values used by the OpenIddict server handler. + /// Default value for . /// - public static class OpenIddictServerAspNetCoreDefaults - { - /// - /// Default value for . - /// - public const string AuthenticationScheme = "OpenIddict.Server.AspNetCore"; - } + public const string AuthenticationScheme = "OpenIddict.Server.AspNetCore"; } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreExtensions.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreExtensions.cs index caa33dee..80053cd0 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreExtensions.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreExtensions.cs @@ -14,85 +14,84 @@ using OpenIddict.Server.AspNetCore; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlerFilters; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict server services. +/// +public static class OpenIddictServerAspNetCoreExtensions { /// - /// Exposes extensions allowing to register the OpenIddict server services. + /// Registers the OpenIddict server services for ASP.NET Core in the DI container. /// - public static class OpenIddictServerAspNetCoreExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerAspNetCoreBuilder UseAspNetCore(this OpenIddictServerBuilder builder) { - /// - /// Registers the OpenIddict server services for ASP.NET Core in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictServerAspNetCoreBuilder UseAspNetCore(this OpenIddictServerBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddAuthentication(); + builder.Services.AddAuthentication(); - builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); - // Register the built-in event handlers used by the OpenIddict ASP.NET Core server components. - // Note: the order used here is not important, as the actual order is set in the options. - builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); + // Register the built-in event handlers used by the OpenIddict ASP.NET Core server components. + // Note: the order used here is not important, as the actual order is set in the options. + builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); - // Register the built-in filters used by the default OpenIddict ASP.NET Core server event handlers. - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); + // Register the built-in filters used by the default OpenIddict ASP.NET Core server event handlers. + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); - // Register the option initializer used by the OpenIddict ASP.NET Core server integration services. - // Note: TryAddEnumerable() is used here to ensure the initializers are only registered once. - builder.Services.TryAddEnumerable(new[] - { - ServiceDescriptor.Singleton, OpenIddictServerAspNetCoreConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictServerAspNetCoreConfiguration>(), + // Register the option initializer used by the OpenIddict ASP.NET Core server integration services. + // Note: TryAddEnumerable() is used here to ensure the initializers are only registered once. + builder.Services.TryAddEnumerable(new[] + { + ServiceDescriptor.Singleton, OpenIddictServerAspNetCoreConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictServerAspNetCoreConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictServerAspNetCoreConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictServerAspNetCoreConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictServerAspNetCoreConfiguration>() - }); + ServiceDescriptor.Singleton, OpenIddictServerAspNetCoreConfiguration>() + }); - return new OpenIddictServerAspNetCoreBuilder(builder.Services); - } + return new OpenIddictServerAspNetCoreBuilder(builder.Services); + } - /// - /// Registers the OpenIddict server services for ASP.NET Core in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the server services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictServerBuilder UseAspNetCore( - this OpenIddictServerBuilder builder, Action configuration) + /// + /// Registers the OpenIddict server services for ASP.NET Core in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the server services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerBuilder UseAspNetCore( + this OpenIddictServerBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseAspNetCore()); + configuration(builder.UseAspNetCore()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreFeature.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreFeature.cs index 1961eb65..46c3db41 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreFeature.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreFeature.cs @@ -4,17 +4,16 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +/// +/// Exposes the current server transaction to the ASP.NET Core host. +/// +public class OpenIddictServerAspNetCoreFeature { /// - /// Exposes the current server transaction to the ASP.NET Core host. + /// Gets or sets the server transaction that encapsulates all specific + /// information about an individual OpenID Connect server request. /// - public class OpenIddictServerAspNetCoreFeature - { - /// - /// Gets or sets the server transaction that encapsulates all specific - /// information about an individual OpenID Connect server request. - /// - public OpenIddictServerTransaction? Transaction { get; set; } - } + public OpenIddictServerTransaction? Transaction { get; set; } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs index 563a3fac..93a6f10e 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs @@ -19,400 +19,399 @@ using static OpenIddict.Server.OpenIddictServerEvents; using Properties = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +/// +/// Provides the logic necessary to extract, validate and handle OpenID Connect requests. +/// +public class OpenIddictServerAspNetCoreHandler : AuthenticationHandler, + IAuthenticationRequestHandler, + IAuthenticationSignInHandler, + IAuthenticationSignOutHandler { + private readonly IOpenIddictServerDispatcher _dispatcher; + private readonly IOpenIddictServerFactory _factory; + /// - /// Provides the logic necessary to extract, validate and handle OpenID Connect requests. + /// Creates a new instance of the class. /// - public class OpenIddictServerAspNetCoreHandler : AuthenticationHandler, - IAuthenticationRequestHandler, - IAuthenticationSignInHandler, - IAuthenticationSignOutHandler + public OpenIddictServerAspNetCoreHandler( + IOpenIddictServerDispatcher dispatcher, + IOpenIddictServerFactory factory, + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock) + : base(options, logger, encoder, clock) + { + _dispatcher = dispatcher; + _factory = factory; + } + + /// + public async Task HandleRequestAsync() { - private readonly IOpenIddictServerDispatcher _dispatcher; - private readonly IOpenIddictServerFactory _factory; - - /// - /// Creates a new instance of the class. - /// - public OpenIddictServerAspNetCoreHandler( - IOpenIddictServerDispatcher dispatcher, - IOpenIddictServerFactory factory, - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder, - ISystemClock clock) - : base(options, logger, encoder, clock) + // Note: the transaction may be already attached when replaying an ASP.NET Core request + // (e.g when using the built-in status code pages middleware with the re-execute mode). + var transaction = Context.Features.Get()?.Transaction; + if (transaction is null) { - _dispatcher = dispatcher; - _factory = factory; + // Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers. + transaction = await _factory.CreateTransactionAsync(); + transaction.Properties[typeof(HttpRequest).FullName!] = new WeakReference(Request); + + // Attach the OpenIddict server transaction to the ASP.NET Core features + // so that it can retrieved while performing sign-in/sign-out operations. + Context.Features.Set(new OpenIddictServerAspNetCoreFeature { Transaction = transaction }); } - /// - public async Task HandleRequestAsync() + var context = new ProcessRequestContext(transaction); + await _dispatcher.DispatchAsync(context); + + if (context.IsRequestHandled) { - // Note: the transaction may be already attached when replaying an ASP.NET Core request - // (e.g when using the built-in status code pages middleware with the re-execute mode). - var transaction = Context.Features.Get()?.Transaction; - if (transaction is null) - { - // Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers. - transaction = await _factory.CreateTransactionAsync(); - transaction.Properties[typeof(HttpRequest).FullName!] = new WeakReference(Request); + return true; + } - // Attach the OpenIddict server transaction to the ASP.NET Core features - // so that it can retrieved while performing sign-in/sign-out operations. - Context.Features.Set(new OpenIddictServerAspNetCoreFeature { Transaction = transaction }); - } + else if (context.IsRequestSkipped) + { + return false; + } - var context = new ProcessRequestContext(transaction); - await _dispatcher.DispatchAsync(context); + else if (context.IsRejected) + { + var notification = new ProcessErrorContext(transaction) + { + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, + Response = new OpenIddictResponse() + }; - if (context.IsRequestHandled) + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) { return true; } - else if (context.IsRequestSkipped) + else if (notification.IsRequestSkipped) { return false; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; - - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled) - { - return true; - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); + } - else if (notification.IsRequestSkipped) - { - return false; - } + return false; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - } + /// + protected override async Task HandleAuthenticateAsync() + { + var transaction = Context.Features.Get()?.Transaction ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + + // Note: in many cases, the authentication token was already validated by the time this action is called + // (generally later in the pipeline, when using the pass-through mode). To avoid having to re-validate it, + // the authentication context is resolved from the transaction. If it's not available, a new one is created. + var context = transaction.GetProperty(typeof(ProcessAuthenticationContext).FullName!); + if (context is null) + { + context = new ProcessAuthenticationContext(transaction); + await _dispatcher.DispatchAsync(context); - return false; + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the authentication result without triggering a new authentication flow. + transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, context); } - /// - protected override async Task HandleAuthenticateAsync() + if (context.IsRequestHandled || context.IsRequestSkipped) { - var transaction = Context.Features.Get()?.Transaction ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); - - // Note: in many cases, the authentication token was already validated by the time this action is called - // (generally later in the pipeline, when using the pass-through mode). To avoid having to re-validate it, - // the authentication context is resolved from the transaction. If it's not available, a new one is created. - var context = transaction.GetProperty(typeof(ProcessAuthenticationContext).FullName!); - if (context is null) - { - context = new ProcessAuthenticationContext(transaction); - await _dispatcher.DispatchAsync(context); - - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the authentication result without triggering a new authentication flow. - transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, context); - } + return AuthenticateResult.NoResult(); + } - if (context.IsRequestHandled || context.IsRequestSkipped) + else if (context.IsRejected) + { + // Note: the missing_token error is special-cased to indicate to ASP.NET Core + // that no authentication result could be produced due to the lack of token. + // This also helps reducing the logging noise when no token is specified. + if (string.Equals(context.Error, Errors.MissingToken, StringComparison.Ordinal)) { return AuthenticateResult.NoResult(); } - else if (context.IsRejected) + var properties = new AuthenticationProperties(new Dictionary { - // Note: the missing_token error is special-cased to indicate to ASP.NET Core - // that no authentication result could be produced due to the lack of token. - // This also helps reducing the logging noise when no token is specified. - if (string.Equals(context.Error, Errors.MissingToken, StringComparison.Ordinal)) - { - return AuthenticateResult.NoResult(); - } + [Properties.Error] = context.Error, + [Properties.ErrorDescription] = context.ErrorDescription, + [Properties.ErrorUri] = context.ErrorUri + }); - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); - - return AuthenticateResult.Fail(SR.GetResourceString(SR.ID0113), properties); - } + return AuthenticateResult.Fail(SR.GetResourceString(SR.ID0113), properties); + } - else + else + { + // A single main claims-based principal instance can be attached to an authentication ticket. + // To return the most appropriate one, the principal is selected based on the endpoint type. + // Independently of the selected main principal, all principals resolved from validated tokens + // are attached to the authentication properties bag so they can be accessed from user code. + var principal = context.EndpointType switch { - // A single main claims-based principal instance can be attached to an authentication ticket. - // To return the most appropriate one, the principal is selected based on the endpoint type. - // Independently of the selected main principal, all principals resolved from validated tokens - // are attached to the authentication properties bag so they can be accessed from user code. - var principal = context.EndpointType switch - { - OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout - => context.IdentityTokenPrincipal, + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout + => context.IdentityTokenPrincipal, - OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation - => context.AccessTokenPrincipal ?? - context.RefreshTokenPrincipal ?? - context.IdentityTokenPrincipal ?? - context.AuthorizationCodePrincipal ?? - context.DeviceCodePrincipal ?? - context.UserCodePrincipal, + OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation + => context.AccessTokenPrincipal ?? + context.RefreshTokenPrincipal ?? + context.IdentityTokenPrincipal ?? + context.AuthorizationCodePrincipal ?? + context.DeviceCodePrincipal ?? + context.UserCodePrincipal, - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => context.AuthorizationCodePrincipal, - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => context.DeviceCodePrincipal, - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => context.RefreshTokenPrincipal, + OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + => context.AuthorizationCodePrincipal, + OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + => context.DeviceCodePrincipal, + OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + => context.RefreshTokenPrincipal, - OpenIddictServerEndpointType.Userinfo => context.AccessTokenPrincipal, + OpenIddictServerEndpointType.Userinfo => context.AccessTokenPrincipal, - OpenIddictServerEndpointType.Verification => context.UserCodePrincipal, + OpenIddictServerEndpointType.Verification => context.UserCodePrincipal, - _ => null - }; + _ => null + }; - if (principal is null) - { - return AuthenticateResult.NoResult(); - } + if (principal is null) + { + return AuthenticateResult.NoResult(); + } - var properties = new AuthenticationProperties - { - ExpiresUtc = principal.GetExpirationDate(), - IssuedUtc = principal.GetCreationDate() - }; + var properties = new AuthenticationProperties + { + ExpiresUtc = principal.GetExpirationDate(), + IssuedUtc = principal.GetCreationDate() + }; - List? tokens = null; + List? tokens = null; - // Attach the tokens to allow any ASP.NET Core component (e.g a controller) - // to retrieve them (e.g to make an API request to another application). + // Attach the tokens to allow any ASP.NET Core component (e.g a controller) + // to retrieve them (e.g to make an API request to another application). - if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken)) + if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken)) + { + tokens ??= new(capacity: 1); + tokens.Add(new AuthenticationToken { - tokens ??= new(capacity: 1); - tokens.Add(new AuthenticationToken - { - Name = TokenTypeHints.AccessToken, - Value = context.AccessToken - }); + Name = TokenTypeHints.AccessToken, + Value = context.AccessToken + }); - properties.SetParameter(Properties.AccessTokenPrincipal, context.AccessTokenPrincipal); - } + properties.SetParameter(Properties.AccessTokenPrincipal, context.AccessTokenPrincipal); + } - if (context.AuthorizationCodePrincipal is not null && !string.IsNullOrEmpty(context.AuthorizationCode)) + if (context.AuthorizationCodePrincipal is not null && !string.IsNullOrEmpty(context.AuthorizationCode)) + { + tokens ??= new(capacity: 1); + tokens.Add(new AuthenticationToken { - tokens ??= new(capacity: 1); - tokens.Add(new AuthenticationToken - { - Name = TokenTypeHints.AuthorizationCode, - Value = context.AuthorizationCode - }); + Name = TokenTypeHints.AuthorizationCode, + Value = context.AuthorizationCode + }); - properties.SetParameter(Properties.AuthorizationCodePrincipal, context.AuthorizationCodePrincipal); - } + properties.SetParameter(Properties.AuthorizationCodePrincipal, context.AuthorizationCodePrincipal); + } - if (context.DeviceCodePrincipal is not null && !string.IsNullOrEmpty(context.DeviceCode)) + if (context.DeviceCodePrincipal is not null && !string.IsNullOrEmpty(context.DeviceCode)) + { + tokens ??= new(capacity: 1); + tokens.Add(new AuthenticationToken { - tokens ??= new(capacity: 1); - tokens.Add(new AuthenticationToken - { - Name = TokenTypeHints.DeviceCode, - Value = context.DeviceCode - }); + Name = TokenTypeHints.DeviceCode, + Value = context.DeviceCode + }); - properties.SetParameter(Properties.DeviceCodePrincipal, context.DeviceCodePrincipal); - } + properties.SetParameter(Properties.DeviceCodePrincipal, context.DeviceCodePrincipal); + } - if (context.IdentityTokenPrincipal is not null && !string.IsNullOrEmpty(context.IdentityToken)) + if (context.IdentityTokenPrincipal is not null && !string.IsNullOrEmpty(context.IdentityToken)) + { + tokens ??= new(capacity: 1); + tokens.Add(new AuthenticationToken { - tokens ??= new(capacity: 1); - tokens.Add(new AuthenticationToken - { - Name = TokenTypeHints.IdToken, - Value = context.IdentityToken - }); + Name = TokenTypeHints.IdToken, + Value = context.IdentityToken + }); - properties.SetParameter(Properties.IdentityTokenPrincipal, context.IdentityTokenPrincipal); - } + properties.SetParameter(Properties.IdentityTokenPrincipal, context.IdentityTokenPrincipal); + } - if (context.RefreshTokenPrincipal is not null && !string.IsNullOrEmpty(context.RefreshToken)) + if (context.RefreshTokenPrincipal is not null && !string.IsNullOrEmpty(context.RefreshToken)) + { + tokens ??= new(capacity: 1); + tokens.Add(new AuthenticationToken { - tokens ??= new(capacity: 1); - tokens.Add(new AuthenticationToken - { - Name = TokenTypeHints.RefreshToken, - Value = context.RefreshToken - }); + Name = TokenTypeHints.RefreshToken, + Value = context.RefreshToken + }); - properties.SetParameter(Properties.RefreshTokenPrincipal, context.RefreshTokenPrincipal); - } + properties.SetParameter(Properties.RefreshTokenPrincipal, context.RefreshTokenPrincipal); + } - if (context.UserCodePrincipal is not null && !string.IsNullOrEmpty(context.UserCode)) + if (context.UserCodePrincipal is not null && !string.IsNullOrEmpty(context.UserCode)) + { + tokens ??= new(capacity: 1); + tokens.Add(new AuthenticationToken { - tokens ??= new(capacity: 1); - tokens.Add(new AuthenticationToken - { - Name = TokenTypeHints.UserCode, - Value = context.UserCode - }); - - properties.SetParameter(Properties.UserCodePrincipal, context.UserCodePrincipal); - } + Name = TokenTypeHints.UserCode, + Value = context.UserCode + }); - if (tokens is { Count: > 0 }) - { - properties.StoreTokens(tokens); - } + properties.SetParameter(Properties.UserCodePrincipal, context.UserCodePrincipal); + } - return AuthenticateResult.Success(new AuthenticationTicket(principal, properties, - OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)); + if (tokens is { Count: > 0 }) + { + properties.StoreTokens(tokens); } + + return AuthenticateResult.Success(new AuthenticationTicket(principal, properties, + OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)); } + } - /// - protected override async Task HandleChallengeAsync(AuthenticationProperties? properties) + /// + protected override async Task HandleChallengeAsync(AuthenticationProperties? properties) + { + var transaction = Context.Features.Get()?.Transaction ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + + transaction.Properties[typeof(AuthenticationProperties).FullName!] = properties ?? new AuthenticationProperties(); + + var context = new ProcessChallengeContext(transaction) { - var transaction = Context.Features.Get()?.Transaction ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + Response = new OpenIddictResponse() + }; + + await _dispatcher.DispatchAsync(context); - transaction.Properties[typeof(AuthenticationProperties).FullName!] = properties ?? new AuthenticationProperties(); + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return; + } - var context = new ProcessChallengeContext(transaction) + else if (context.IsRejected) + { + var notification = new ProcessErrorContext(transaction) { + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, Response = new OpenIddictResponse() }; - await _dispatcher.DispatchAsync(context); + await _dispatcher.DispatchAsync(notification); - if (context.IsRequestHandled || context.IsRequestSkipped) + if (notification.IsRequestHandled || context.IsRequestSkipped) { return; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; - - await _dispatcher.DispatchAsync(notification); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); + } + } - if (notification.IsRequestHandled || context.IsRequestSkipped) - { - return; - } + /// + protected override Task HandleForbiddenAsync(AuthenticationProperties? properties) + => HandleChallengeAsync(properties); - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - } + /// + public async Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties) + { + if (user is null) + { + throw new ArgumentNullException(nameof(user)); } - /// - protected override Task HandleForbiddenAsync(AuthenticationProperties? properties) - => HandleChallengeAsync(properties); + var transaction = Context.Features.Get()?.Transaction ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); - /// - public async Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties) + transaction.Properties[typeof(AuthenticationProperties).FullName!] = properties ?? new AuthenticationProperties(); + + var context = new ProcessSignInContext(transaction) { - if (user is null) - { - throw new ArgumentNullException(nameof(user)); - } + Principal = user, + Response = new OpenIddictResponse() + }; - var transaction = Context.Features.Get()?.Transaction ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + await _dispatcher.DispatchAsync(context); - transaction.Properties[typeof(AuthenticationProperties).FullName!] = properties ?? new AuthenticationProperties(); + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return; + } - var context = new ProcessSignInContext(transaction) + else if (context.IsRejected) + { + var notification = new ProcessErrorContext(transaction) { - Principal = user, + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, Response = new OpenIddictResponse() }; - await _dispatcher.DispatchAsync(context); + await _dispatcher.DispatchAsync(notification); - if (context.IsRequestHandled || context.IsRequestSkipped) + if (notification.IsRequestHandled || context.IsRequestSkipped) { return; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); + } + } - await _dispatcher.DispatchAsync(notification); + /// + public async Task SignOutAsync(AuthenticationProperties? properties) + { + var transaction = Context.Features.Get()?.Transaction ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); - if (notification.IsRequestHandled || context.IsRequestSkipped) - { - return; - } + var context = new ProcessSignOutContext(transaction) + { + Response = new OpenIddictResponse() + }; - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - } - } + transaction.Properties[typeof(AuthenticationProperties).FullName!] = properties ?? new AuthenticationProperties(); + + await _dispatcher.DispatchAsync(context); - /// - public async Task SignOutAsync(AuthenticationProperties? properties) + if (context.IsRequestHandled || context.IsRequestSkipped) { - var transaction = Context.Features.Get()?.Transaction ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + return; + } - var context = new ProcessSignOutContext(transaction) + else if (context.IsRejected) + { + var notification = new ProcessErrorContext(transaction) { + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, Response = new OpenIddictResponse() }; - transaction.Properties[typeof(AuthenticationProperties).FullName!] = properties ?? new AuthenticationProperties(); - - await _dispatcher.DispatchAsync(context); + await _dispatcher.DispatchAsync(notification); - if (context.IsRequestHandled || context.IsRequestSkipped) + if (notification.IsRequestHandled || context.IsRequestSkipped) { return; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; - - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled || context.IsRequestSkipped) - { - return; - } - - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); } } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlerFilters.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlerFilters.cs index e598b117..cbc6907b 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlerFilters.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlerFilters.cs @@ -11,243 +11,242 @@ using Microsoft.AspNetCore; using Microsoft.Extensions.Options; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +/// +/// Contains a collection of event handler filters commonly used by the ASP.NET Core handlers. +/// +[EditorBrowsable(EditorBrowsableState.Advanced)] +public static class OpenIddictServerAspNetCoreHandlerFilters { /// - /// Contains a collection of event handler filters commonly used by the ASP.NET Core handlers. + /// Represents a filter that excludes the associated handlers if authorization request caching was not enabled. /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static class OpenIddictServerAspNetCoreHandlerFilters + public class RequireAuthorizationRequestCachingEnabled : IOpenIddictServerHandlerFilter { - /// - /// Represents a filter that excludes the associated handlers if authorization request caching was not enabled. - /// - public class RequireAuthorizationRequestCachingEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + private readonly IOptionsMonitor _options; - public RequireAuthorizationRequestCachingEnabled(IOptionsMonitor options) - => _options = options; + public RequireAuthorizationRequestCachingEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableAuthorizationRequestCaching); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableAuthorizationRequestCaching); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the authorization endpoint. - /// - public class RequireAuthorizationEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the authorization endpoint. + /// + public class RequireAuthorizationEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireAuthorizationEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireAuthorizationEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableAuthorizationEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableAuthorizationEndpointPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if error pass-through was not enabled. - /// - public class RequireErrorPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if error pass-through was not enabled. + /// + public class RequireErrorPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireErrorPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireErrorPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableErrorPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableErrorPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if no ASP.NET Core request can be found. - /// - public class RequireHttpRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no ASP.NET Core request can be found. + /// + public class RequireHttpRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Transaction.GetHttpRequest() is not null); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Transaction.GetHttpRequest() is not null); } + } - /// - /// Represents a filter that excludes the associated handlers if logout request caching was not enabled. - /// - public class RequireLogoutRequestCachingEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if logout request caching was not enabled. + /// + public class RequireLogoutRequestCachingEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireLogoutRequestCachingEnabled(IOptionsMonitor options) - => _options = options; + public RequireLogoutRequestCachingEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableLogoutRequestCaching); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableLogoutRequestCaching); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the logout endpoint. - /// - public class RequireLogoutEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the logout endpoint. + /// + public class RequireLogoutEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireLogoutEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireLogoutEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableLogoutEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableLogoutEndpointPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if status code pages support was not enabled. - /// - public class RequireStatusCodePagesIntegrationEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if status code pages support was not enabled. + /// + public class RequireStatusCodePagesIntegrationEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireStatusCodePagesIntegrationEnabled(IOptionsMonitor options) - => _options = options; + public RequireStatusCodePagesIntegrationEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableStatusCodePagesIntegration); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableStatusCodePagesIntegration); } + } - /// - /// Represents a filter that excludes the associated handlers if the HTTPS requirement was disabled. - /// - public class RequireTransportSecurityRequirementEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the HTTPS requirement was disabled. + /// + public class RequireTransportSecurityRequirementEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireTransportSecurityRequirementEnabled(IOptionsMonitor options) - => _options = options; + public RequireTransportSecurityRequirementEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!_options.CurrentValue.DisableTransportSecurityRequirement); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!_options.CurrentValue.DisableTransportSecurityRequirement); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the authorization endpoint. - /// - public class RequireTokenEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the authorization endpoint. + /// + public class RequireTokenEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireTokenEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireTokenEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableTokenEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableTokenEndpointPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the userinfo endpoint. - /// - public class RequireUserinfoEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the userinfo endpoint. + /// + public class RequireUserinfoEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireUserinfoEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireUserinfoEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableUserinfoEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableUserinfoEndpointPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the verification endpoint. - /// - public class RequireVerificationEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the verification endpoint. + /// + public class RequireVerificationEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireVerificationEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireVerificationEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableVerificationEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableVerificationEndpointPassthrough); } } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs index d7203911..4ce3522d 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Authentication.cs @@ -30,530 +30,529 @@ using static OpenIddict.Server.OpenIddictServerEvents; using JsonWebTokenTypes = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.JsonWebTokenTypes; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +public static partial class OpenIddictServerAspNetCoreHandlers { - public static partial class OpenIddictServerAspNetCoreHandlers + public static class Authentication { - public static class Authentication + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Authorization request extraction: + */ + ExtractGetOrPostRequest.Descriptor, + RestoreCachedRequestParameters.Descriptor, + CacheRequestParameters.Descriptor, + + /* + * Authorization request handling: + */ + EnablePassthroughMode.Descriptor, + + /* + * Authorization response processing: + */ + RemoveCachedRequest.Descriptor, + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + ProcessFormPostResponse.Descriptor, + ProcessQueryResponse.Descriptor, + ProcessFragmentResponse.Descriptor, + ProcessPassthroughErrorResponse.Descriptor, + ProcessStatusCodePagesErrorResponse.Descriptor, + ProcessLocalErrorResponse.Descriptor); + + /// + /// Contains the logic responsible of restoring cached requests from the request_id, if specified. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class RestoreCachedRequestParameters : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Authorization request extraction: - */ - ExtractGetOrPostRequest.Descriptor, - RestoreCachedRequestParameters.Descriptor, - CacheRequestParameters.Descriptor, - - /* - * Authorization request handling: - */ - EnablePassthroughMode.Descriptor, - - /* - * Authorization response processing: - */ - RemoveCachedRequest.Descriptor, - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - ProcessFormPostResponse.Descriptor, - ProcessQueryResponse.Descriptor, - ProcessFragmentResponse.Descriptor, - ProcessPassthroughErrorResponse.Descriptor, - ProcessStatusCodePagesErrorResponse.Descriptor, - ProcessLocalErrorResponse.Descriptor); + private readonly IDistributedCache _cache; + + public RestoreCachedRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public RestoreCachedRequestParameters(IDistributedCache cache) + => _cache = cache; /// - /// Contains the logic responsible of restoring cached requests from the request_id, if specified. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class RestoreCachedRequestParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ExtractAuthorizationRequestContext context) { - private readonly IDistributedCache _cache; - - public RestoreCachedRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - - public RestoreCachedRequestParameters(IDistributedCache cache) - => _cache = cache; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ExtractAuthorizationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); + Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - // If a request_id parameter can be found in the authorization request, - // restore the complete authorization request from the distributed cache. + // If a request_id parameter can be found in the authorization request, + // restore the complete authorization request from the distributed cache. - if (string.IsNullOrEmpty(context.Request.RequestId)) - { - return; - } + if (string.IsNullOrEmpty(context.Request.RequestId)) + { + return; + } - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - var token = await _cache.GetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId); - if (token is null || !context.Options.JsonWebTokenHandler.CanReadToken(token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6146), Parameters.RequestId); + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + var token = await _cache.GetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId); + if (token is null || !context.Options.JsonWebTokenHandler.CanReadToken(token)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6146), Parameters.RequestId); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.RequestId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.RequestId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } - var parameters = context.Options.TokenValidationParameters.Clone(); - parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; - parameters.ValidAudience = context.Issuer?.AbsoluteUri; - parameters.ValidTypes = new[] { JsonWebTokenTypes.Private.AuthorizationRequest }; + var parameters = context.Options.TokenValidationParameters.Clone(); + parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; + parameters.ValidAudience = context.Issuer?.AbsoluteUri; + parameters.ValidTypes = new[] { JsonWebTokenTypes.Private.AuthorizationRequest }; - var result = context.Options.JsonWebTokenHandler.ValidateToken(token, parameters); - if (!result.IsValid) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6146), Parameters.RequestId); + var result = context.Options.JsonWebTokenHandler.ValidateToken(token, parameters); + if (!result.IsValid) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6146), Parameters.RequestId); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.RequestId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.RequestId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } - using var document = JsonDocument.Parse( - Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload)); - if (document.RootElement.ValueKind != JsonValueKind.Object) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0117)); - } + using var document = JsonDocument.Parse( + Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload)); + if (document.RootElement.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0117)); + } - // Restore the authorization request parameters from the serialized payload. - foreach (var parameter in document.RootElement.EnumerateObject()) + // Restore the authorization request parameters from the serialized payload. + foreach (var parameter in document.RootElement.EnumerateObject()) + { + // Avoid overriding the current request parameters. + if (context.Request.HasParameter(parameter.Name)) { - // Avoid overriding the current request parameters. - if (context.Request.HasParameter(parameter.Name)) - { - continue; - } - - context.Request.SetParameter(parameter.Name, parameter.Value.Clone()); + continue; } + + context.Request.SetParameter(parameter.Name, parameter.Value.Clone()); } } + } + + /// + /// Contains the logic responsible of caching authorization requests, if applicable. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class CacheRequestParameters : IOpenIddictServerHandler + { + private readonly IDistributedCache _cache; + private readonly IOptionsMonitor _options; + + public CacheRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public CacheRequestParameters( + IDistributedCache cache, + IOptionsMonitor options) + { + _cache = cache; + _options = options; + } /// - /// Contains the logic responsible of caching authorization requests, if applicable. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class CacheRequestParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(RestoreCachedRequestParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ExtractAuthorizationRequestContext context) { - private readonly IDistributedCache _cache; - private readonly IOptionsMonitor _options; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public CacheRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - public CacheRequestParameters( - IDistributedCache cache, - IOptionsMonitor options) + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) { - _cache = cache; - _options = options; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); } - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(RestoreCachedRequestParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ExtractAuthorizationRequestContext context) + // Don't cache the request if the request doesn't include any parameter. + // If a request_id parameter can be found in the authorization request, + // ignore the following logic to prevent an infinite redirect loop. + if (context.Request.Count == 0 || !string.IsNullOrEmpty(context.Request.RequestId)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - // Don't cache the request if the request doesn't include any parameter. - // If a request_id parameter can be found in the authorization request, - // ignore the following logic to prevent an infinite redirect loop. - if (context.Request.Count == 0 || !string.IsNullOrEmpty(context.Request.RequestId)) - { - return; - } + return; + } - // Generate a 256-bit request identifier using a crypto-secure random number generator. - var data = new byte[256 / 8]; + // Generate a 256-bit request identifier using a crypto-secure random number generator. + var data = new byte[256 / 8]; #if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); + RandomNumberGenerator.Fill(data); #else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); + using var generator = RandomNumberGenerator.Create(); + generator.GetBytes(data); #endif - context.Request.RequestId = Base64UrlEncoder.Encode(data); + context.Request.RequestId = Base64UrlEncoder.Encode(data); - // Store the serialized authorization request parameters in the distributed cache. - var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor - { - Audience = context.Issuer?.AbsoluteUri, - Claims = context.Request.GetParameters().ToDictionary( - parameter => parameter.Key, - parameter => parameter.Value.Value), - EncryptingCredentials = context.Options.EncryptionCredentials.First(), - Issuer = context.Issuer?.AbsoluteUri, - SigningCredentials = context.Options.SigningCredentials.First(), - Subject = new ClaimsIdentity(), - TokenType = JsonWebTokenTypes.Private.AuthorizationRequest - }); - - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - await _cache.SetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId, - token, _options.CurrentValue.AuthorizationRequestCachingPolicy); - - // Create a new GET authorization request containing only the request_id parameter. - var address = QueryHelpers.AddQueryString( - uri: request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase + request.Path, - name: Parameters.RequestId, - value: context.Request.RequestId); - - request.HttpContext.Response.Redirect(address); - - // Mark the response as handled to skip the rest of the pipeline. - context.HandleRequest(); - } + // Store the serialized authorization request parameters in the distributed cache. + var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor + { + Audience = context.Issuer?.AbsoluteUri, + Claims = context.Request.GetParameters().ToDictionary( + parameter => parameter.Key, + parameter => parameter.Value.Value), + EncryptingCredentials = context.Options.EncryptionCredentials.First(), + Issuer = context.Issuer?.AbsoluteUri, + SigningCredentials = context.Options.SigningCredentials.First(), + Subject = new ClaimsIdentity(), + TokenType = JsonWebTokenTypes.Private.AuthorizationRequest + }); + + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + await _cache.SetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId, + token, _options.CurrentValue.AuthorizationRequestCachingPolicy); + + // Create a new GET authorization request containing only the request_id parameter. + var address = QueryHelpers.AddQueryString( + uri: request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase + request.Path, + name: Parameters.RequestId, + value: context.Request.RequestId); + + request.HttpContext.Response.Redirect(address); + + // Mark the response as handled to skip the rest of the pipeline. + context.HandleRequest(); } + } + + /// + /// Contains the logic responsible of removing cached authorization requests from the distributed cache. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class RemoveCachedRequest : IOpenIddictServerHandler + { + private readonly IDistributedCache _cache; + + public RemoveCachedRequest() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public RemoveCachedRequest(IDistributedCache cache) + => _cache = cache; /// - /// Contains the logic responsible of removing cached authorization requests from the distributed cache. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class RemoveCachedRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) { - private readonly IDistributedCache _cache; - - public RemoveCachedRequest() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - - public RemoveCachedRequest(IDistributedCache cache) - => _cache = cache; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - if (string.IsNullOrEmpty(context.Request?.RequestId)) - { - return default; - } + if (string.IsNullOrEmpty(context.Request?.RequestId)) + { + return default; + } - // Note: the ApplyAuthorizationResponse event is called for both successful - // and errored authorization responses but discrimination is not necessary here, - // as the authorization request must be removed from the distributed cache in both cases. + // Note: the ApplyAuthorizationResponse event is called for both successful + // and errored authorization responses but discrimination is not necessary here, + // as the authorization request must be removed from the distributed cache in both cases. - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - return new ValueTask(_cache.RemoveAsync(Cache.AuthorizationRequest + context.Request.RequestId)); - } + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + return new ValueTask(_cache.RemoveAsync(Cache.AuthorizationRequest + context.Request.RequestId)); } + } + + /// + /// Contains the logic responsible of processing authorization responses using the form_post response mode. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessFormPostResponse : IOpenIddictServerHandler + { + private readonly HtmlEncoder _encoder; + + public ProcessFormPostResponse(HtmlEncoder encoder) + => _encoder = encoder; /// - /// Contains the logic responsible of processing authorization responses using the form_post response mode. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessFormPostResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(50_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ApplyAuthorizationResponseContext context) { - private readonly HtmlEncoder _encoder; - - public ProcessFormPostResponse(HtmlEncoder encoder) - => _encoder = encoder; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(50_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - if (string.IsNullOrEmpty(context.RedirectUri) || - !string.Equals(context.ResponseMode, ResponseModes.FormPost, StringComparison.Ordinal)) - { - return; - } + if (string.IsNullOrEmpty(context.RedirectUri) || + !string.Equals(context.ResponseMode, ResponseModes.FormPost, StringComparison.Ordinal)) + { + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6147), context.RedirectUri, context.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6147), context.RedirectUri, context.Response); - using (var buffer = new MemoryStream()) - using (var writer = new StreamWriter(buffer)) - { - writer.WriteLine(""); - writer.WriteLine(""); - writer.WriteLine(""); - - // While the redirect_uri parameter should be guarded against unknown values, - // it's still safer to encode it to avoid cross-site scripting attacks - // if the authorization server has a relaxed policy concerning redirect URIs. - writer.WriteLine($@"
"); - - // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters - // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. - // For consistency, multiple parameters with the same name are also supported by this endpoint. - foreach (var (key, value) in - from parameter in context.Response.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) - { - writer.WriteLine($@""); - } + using (var buffer = new MemoryStream()) + using (var writer = new StreamWriter(buffer)) + { + writer.WriteLine(""); + writer.WriteLine(""); + writer.WriteLine(""); - writer.WriteLine(@""); - writer.WriteLine("
"); - writer.WriteLine(""); - writer.WriteLine(""); - writer.WriteLine(""); - writer.Flush(); + // While the redirect_uri parameter should be guarded against unknown values, + // it's still safer to encode it to avoid cross-site scripting attacks + // if the authorization server has a relaxed policy concerning redirect URIs. + writer.WriteLine($@"
"); - response.StatusCode = 200; - response.ContentLength = buffer.Length; - response.ContentType = "text/html;charset=UTF-8"; + // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters + // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. + // For consistency, multiple parameters with the same name are also supported by this endpoint. + foreach (var (key, value) in + from parameter in context.Response.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select (parameter.Key, Value: value)) + { + writer.WriteLine($@""); + } - response.Headers["Cache-Control"] = "no-cache"; - response.Headers["Pragma"] = "no-cache"; - response.Headers["Expires"] = "-1"; + writer.WriteLine(@""); + writer.WriteLine("
"); + writer.WriteLine(""); + writer.WriteLine(""); + writer.WriteLine(""); + writer.Flush(); - buffer.Seek(offset: 0, loc: SeekOrigin.Begin); - await buffer.CopyToAsync(response.Body, 4096); - } + response.StatusCode = 200; + response.ContentLength = buffer.Length; + response.ContentType = "text/html;charset=UTF-8"; + + response.Headers["Cache-Control"] = "no-cache"; + response.Headers["Pragma"] = "no-cache"; + response.Headers["Expires"] = "-1"; - context.HandleRequest(); + buffer.Seek(offset: 0, loc: SeekOrigin.Begin); + await buffer.CopyToAsync(response.Body, 4096); } + + context.HandleRequest(); } + } + /// + /// Contains the logic responsible of processing authorization responses using the query response mode. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessQueryResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing authorization responses using the query response mode. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessQueryResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessFormPostResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessFormPostResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - if (string.IsNullOrEmpty(context.RedirectUri) || - !string.Equals(context.ResponseMode, ResponseModes.Query, StringComparison.Ordinal)) - { - return default; - } + if (string.IsNullOrEmpty(context.RedirectUri) || + !string.Equals(context.ResponseMode, ResponseModes.Query, StringComparison.Ordinal)) + { + return default; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6148), context.RedirectUri, context.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6148), context.RedirectUri, context.Response); - // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters - // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. - // For consistency, multiple parameters with the same name are also supported by this endpoint. + // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters + // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. + // For consistency, multiple parameters with the same name are also supported by this endpoint. #if SUPPORTS_MULTIPLE_VALUES_IN_QUERYHELPERS - var location = QueryHelpers.AddQueryString(context.RedirectUri, - from parameter in context.Response.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select KeyValuePair.Create(parameter.Key, value)); + var location = QueryHelpers.AddQueryString(context.RedirectUri, + from parameter in context.Response.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select KeyValuePair.Create(parameter.Key, value)); #else - var location = context.RedirectUri; - - foreach (var (key, value) in - from parameter in context.Response.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) - { - location = QueryHelpers.AddQueryString(location, key, value); - } + var location = context.RedirectUri; + + foreach (var (key, value) in + from parameter in context.Response.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select (parameter.Key, Value: value)) + { + location = QueryHelpers.AddQueryString(location, key, value); + } #endif - response.Redirect(location); - context.HandleRequest(); + response.Redirect(location); + context.HandleRequest(); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of processing authorization responses using the fragment response mode. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessFragmentResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing authorization responses using the fragment response mode. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessFragmentResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessQueryResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessQueryResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - if (string.IsNullOrEmpty(context.RedirectUri) || - !string.Equals(context.ResponseMode, ResponseModes.Fragment, StringComparison.Ordinal)) - { - return default; - } + if (string.IsNullOrEmpty(context.RedirectUri) || + !string.Equals(context.ResponseMode, ResponseModes.Fragment, StringComparison.Ordinal)) + { + return default; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6149), context.RedirectUri, context.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6149), context.RedirectUri, context.Response); - var builder = new StringBuilder(context.RedirectUri); + var builder = new StringBuilder(context.RedirectUri); - // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters - // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. - // For consistency, multiple parameters with the same name are also supported by this endpoint. - foreach (var (key, value) in - from parameter in context.Response.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) - { - builder.Append(Contains(builder, '#') ? '&' : '#') - .Append(Uri.EscapeDataString(key)) - .Append('=') - .Append(Uri.EscapeDataString(value)); - } + // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters + // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. + // For consistency, multiple parameters with the same name are also supported by this endpoint. + foreach (var (key, value) in + from parameter in context.Response.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select (parameter.Key, Value: value)) + { + builder.Append(Contains(builder, '#') ? '&' : '#') + .Append(Uri.EscapeDataString(key)) + .Append('=') + .Append(Uri.EscapeDataString(value)); + } - response.Redirect(builder.ToString()); - context.HandleRequest(); + response.Redirect(builder.ToString()); + context.HandleRequest(); - return default; + return default; - static bool Contains(StringBuilder builder, char delimiter) + static bool Contains(StringBuilder builder, char delimiter) + { + for (var index = 0; index < builder.Length; index++) { - for (var index = 0; index < builder.Length; index++) + if (builder[index] == delimiter) { - if (builder[index] == delimiter) - { - return true; - } + return true; } - - return false; } + + return false; } } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs index 90d262f4..6579fbad 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs @@ -15,101 +15,100 @@ using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlerFilte using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +public static partial class OpenIddictServerAspNetCoreHandlers { - public static partial class OpenIddictServerAspNetCoreHandlers + public static class Device { - public static class Device - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Device request extraction: - */ - ExtractPostRequest.Descriptor, - ExtractBasicAuthenticationCredentials.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Device request extraction: + */ + ExtractPostRequest.Descriptor, + ExtractBasicAuthenticationCredentials.Descriptor, - /* - * Device response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor, + /* + * Device response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor, - /* - * Verification request extraction: - */ - ExtractGetOrPostRequest.Descriptor, + /* + * Verification request extraction: + */ + ExtractGetOrPostRequest.Descriptor, - /* - * Verification request handling: - */ - EnablePassthroughMode.Descriptor, + /* + * Verification request handling: + */ + EnablePassthroughMode.Descriptor, - /* - * Verification response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - ProcessHostRedirectionResponse.Descriptor, - ProcessPassthroughErrorResponse.Descriptor, - ProcessStatusCodePagesErrorResponse.Descriptor, - ProcessLocalErrorResponse.Descriptor, - ProcessEmptyResponse.Descriptor); - } + /* + * Verification response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + ProcessHostRedirectionResponse.Descriptor, + ProcessPassthroughErrorResponse.Descriptor, + ProcessStatusCodePagesErrorResponse.Descriptor, + ProcessLocalErrorResponse.Descriptor, + ProcessEmptyResponse.Descriptor); + } + /// + /// Contains the logic responsible of processing verification responses that should trigger a host redirection. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessHostRedirectionResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing verification responses that should trigger a host redirection. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessHostRedirectionResponse : IOpenIddictServerHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessPassthroughErrorResponse.Descriptor.Order - 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessPassthroughErrorResponse.Descriptor.Order - 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); - /// - public ValueTask HandleAsync(ApplyVerificationResponseContext context) + /// + public ValueTask HandleAsync(ApplyVerificationResponseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + throw new ArgumentNullException(nameof(context)); + } - // Note: this handler only redirects the user agent to the address specified in - // the properties when there's no error or if the error is an access_denied error. - if (!string.IsNullOrEmpty(context.Response.Error) && - !string.Equals(context.Response.Error, Errors.AccessDenied, StringComparison.Ordinal)) - { - return default; - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is not null && !string.IsNullOrEmpty(properties.RedirectUri)) - { - response.Redirect(properties.RedirectUri); + // Note: this handler only redirects the user agent to the address specified in + // the properties when there's no error or if the error is an access_denied error. + if (!string.IsNullOrEmpty(context.Response.Error) && + !string.Equals(context.Response.Error, Errors.AccessDenied, StringComparison.Ordinal)) + { + return default; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6144)); - context.HandleRequest(); - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is not null && !string.IsNullOrEmpty(properties.RedirectUri)) + { + response.Redirect(properties.RedirectUri); - return default; + context.Logger.LogInformation(SR.GetResourceString(SR.ID6144)); + context.HandleRequest(); } + + return default; } } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Discovery.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Discovery.cs index 40c5ff3d..ae001701 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Discovery.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Discovery.cs @@ -7,36 +7,35 @@ using System.Collections.Immutable; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +public static partial class OpenIddictServerAspNetCoreHandlers { - public static partial class OpenIddictServerAspNetCoreHandlers + public static class Discovery { - public static class Discovery - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Configuration request extraction: - */ - ExtractGetRequest.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Configuration request extraction: + */ + ExtractGetRequest.Descriptor, - /* - * Configuration response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor, + /* + * Configuration response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor, - /* - * Cryptography request extraction: - */ - ExtractGetRequest.Descriptor, + /* + * Cryptography request extraction: + */ + ExtractGetRequest.Descriptor, - /* - * Cryptography response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Cryptography response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Exchange.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Exchange.cs index fa6dd284..e9db4b96 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Exchange.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Exchange.cs @@ -8,31 +8,30 @@ using System.Collections.Immutable; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlerFilters; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +public static partial class OpenIddictServerAspNetCoreHandlers { - public static partial class OpenIddictServerAspNetCoreHandlers + public static class Exchange { - public static class Exchange - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Token request extraction: - */ - ExtractPostRequest.Descriptor, - ExtractBasicAuthenticationCredentials.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Token request extraction: + */ + ExtractPostRequest.Descriptor, + ExtractBasicAuthenticationCredentials.Descriptor, - /* - * Token request handling: - */ - EnablePassthroughMode.Descriptor, + /* + * Token request handling: + */ + EnablePassthroughMode.Descriptor, - /* - * Token response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Token response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs index aa0d709e..8b781151 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs @@ -7,25 +7,24 @@ using System.Collections.Immutable; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +public static partial class OpenIddictServerAspNetCoreHandlers { - public static partial class OpenIddictServerAspNetCoreHandlers + public static class Introspection { - public static class Introspection - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Introspection request extraction: - */ - ExtractGetOrPostRequest.Descriptor, - ExtractBasicAuthenticationCredentials.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Introspection request extraction: + */ + ExtractGetOrPostRequest.Descriptor, + ExtractBasicAuthenticationCredentials.Descriptor, - /* - * Introspection response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Introspection response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Revocation.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Revocation.cs index 88166b19..cb5e6e1e 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Revocation.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Revocation.cs @@ -7,26 +7,25 @@ using System.Collections.Immutable; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +public static partial class OpenIddictServerAspNetCoreHandlers { - public static partial class OpenIddictServerAspNetCoreHandlers + public static class Revocation { - public static class Revocation - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Revocation request extraction: - */ - ExtractPostRequest.Descriptor, - ExtractBasicAuthenticationCredentials.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Revocation request extraction: + */ + ExtractPostRequest.Descriptor, + ExtractBasicAuthenticationCredentials.Descriptor, - /* - * Revocation response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Revocation response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs index e6553286..8a0e562b 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Session.cs @@ -28,409 +28,408 @@ using static OpenIddict.Server.OpenIddictServerEvents; using JsonWebTokenTypes = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.JsonWebTokenTypes; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +public static partial class OpenIddictServerAspNetCoreHandlers { - public static partial class OpenIddictServerAspNetCoreHandlers + public static class Session { - public static class Session + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Logout request extraction: + */ + ExtractGetOrPostRequest.Descriptor, + RestoreCachedRequestParameters.Descriptor, + CacheRequestParameters.Descriptor, + + /* + * Logout request handling: + */ + EnablePassthroughMode.Descriptor, + + /* + * Logout response processing: + */ + RemoveCachedRequest.Descriptor, + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + ProcessHostRedirectionResponse.Descriptor, + ProcessPassthroughErrorResponse.Descriptor, + ProcessStatusCodePagesErrorResponse.Descriptor, + ProcessLocalErrorResponse.Descriptor, + ProcessQueryResponse.Descriptor, + ProcessEmptyResponse.Descriptor); + + /// + /// Contains the logic responsible of restoring cached requests from the request_id, if specified. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class RestoreCachedRequestParameters : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Logout request extraction: - */ - ExtractGetOrPostRequest.Descriptor, - RestoreCachedRequestParameters.Descriptor, - CacheRequestParameters.Descriptor, - - /* - * Logout request handling: - */ - EnablePassthroughMode.Descriptor, - - /* - * Logout response processing: - */ - RemoveCachedRequest.Descriptor, - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - ProcessHostRedirectionResponse.Descriptor, - ProcessPassthroughErrorResponse.Descriptor, - ProcessStatusCodePagesErrorResponse.Descriptor, - ProcessLocalErrorResponse.Descriptor, - ProcessQueryResponse.Descriptor, - ProcessEmptyResponse.Descriptor); + private readonly IDistributedCache _cache; + + public RestoreCachedRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public RestoreCachedRequestParameters(IDistributedCache cache) + => _cache = cache; /// - /// Contains the logic responsible of restoring cached requests from the request_id, if specified. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class RestoreCachedRequestParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ExtractLogoutRequestContext context) { - private readonly IDistributedCache _cache; - - public RestoreCachedRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - - public RestoreCachedRequestParameters(IDistributedCache cache) - => _cache = cache; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ExtractLogoutRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); + Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - // If a request_id parameter can be found in the logout request, - // restore the complete logout request from the distributed cache. + // If a request_id parameter can be found in the logout request, + // restore the complete logout request from the distributed cache. - if (string.IsNullOrEmpty(context.Request.RequestId)) - { - return; - } + if (string.IsNullOrEmpty(context.Request.RequestId)) + { + return; + } - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - var token = await _cache.GetStringAsync(Cache.LogoutRequest + context.Request.RequestId); - if (token is null || !context.Options.JsonWebTokenHandler.CanReadToken(token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6150), Parameters.RequestId); + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + var token = await _cache.GetStringAsync(Cache.LogoutRequest + context.Request.RequestId); + if (token is null || !context.Options.JsonWebTokenHandler.CanReadToken(token)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6150), Parameters.RequestId); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.RequestId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.RequestId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } - var parameters = context.Options.TokenValidationParameters.Clone(); - parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; - parameters.ValidAudience = context.Issuer?.AbsoluteUri; - parameters.ValidTypes = new[] { JsonWebTokenTypes.Private.LogoutRequest }; + var parameters = context.Options.TokenValidationParameters.Clone(); + parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; + parameters.ValidAudience = context.Issuer?.AbsoluteUri; + parameters.ValidTypes = new[] { JsonWebTokenTypes.Private.LogoutRequest }; - var result = context.Options.JsonWebTokenHandler.ValidateToken(token, parameters); - if (!result.IsValid) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6150), Parameters.RequestId); + var result = context.Options.JsonWebTokenHandler.ValidateToken(token, parameters); + if (!result.IsValid) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6150), Parameters.RequestId); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.RequestId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.RequestId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } - using var document = JsonDocument.Parse( - Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload)); - if (document.RootElement.ValueKind != JsonValueKind.Object) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0118)); - } + using var document = JsonDocument.Parse( + Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload)); + if (document.RootElement.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0118)); + } - // Restore the authorization request parameters from the serialized payload. - foreach (var parameter in document.RootElement.EnumerateObject()) + // Restore the authorization request parameters from the serialized payload. + foreach (var parameter in document.RootElement.EnumerateObject()) + { + // Avoid overriding the current request parameters. + if (context.Request.HasParameter(parameter.Name)) { - // Avoid overriding the current request parameters. - if (context.Request.HasParameter(parameter.Name)) - { - continue; - } - - context.Request.SetParameter(parameter.Name, parameter.Value.Clone()); + continue; } + + context.Request.SetParameter(parameter.Name, parameter.Value.Clone()); } } + } + + /// + /// Contains the logic responsible of caching logout requests, if applicable. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class CacheRequestParameters : IOpenIddictServerHandler + { + private readonly IDistributedCache _cache; + private readonly IOptionsMonitor _options; + + public CacheRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public CacheRequestParameters( + IDistributedCache cache, + IOptionsMonitor options) + { + _cache = cache; + _options = options; + } /// - /// Contains the logic responsible of caching logout requests, if applicable. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class CacheRequestParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(RestoreCachedRequestParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ExtractLogoutRequestContext context) { - private readonly IDistributedCache _cache; - private readonly IOptionsMonitor _options; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public CacheRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - public CacheRequestParameters( - IDistributedCache cache, - IOptionsMonitor options) + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) { - _cache = cache; - _options = options; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); } - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(RestoreCachedRequestParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ExtractLogoutRequestContext context) + // Don't cache the request if the request doesn't include any parameter. + // If a request_id parameter can be found in the logout request, + // ignore the following logic to prevent an infinite redirect loop. + if (context.Request.Count == 0 || !string.IsNullOrEmpty(context.Request.RequestId)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - // Don't cache the request if the request doesn't include any parameter. - // If a request_id parameter can be found in the logout request, - // ignore the following logic to prevent an infinite redirect loop. - if (context.Request.Count == 0 || !string.IsNullOrEmpty(context.Request.RequestId)) - { - return; - } + return; + } - // Generate a 256-bit request identifier using a crypto-secure random number generator. - var data = new byte[256 / 8]; + // Generate a 256-bit request identifier using a crypto-secure random number generator. + var data = new byte[256 / 8]; #if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); + RandomNumberGenerator.Fill(data); #else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); + using var generator = RandomNumberGenerator.Create(); + generator.GetBytes(data); #endif - context.Request.RequestId = Base64UrlEncoder.Encode(data); + context.Request.RequestId = Base64UrlEncoder.Encode(data); - // Store the serialized logout request parameters in the distributed cache. - var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor - { - Audience = context.Issuer?.AbsoluteUri, - Claims = context.Request.GetParameters().ToDictionary( - parameter => parameter.Key, - parameter => parameter.Value.Value), - EncryptingCredentials = context.Options.EncryptionCredentials.First(), - Issuer = context.Issuer?.AbsoluteUri, - SigningCredentials = context.Options.SigningCredentials.First(), - Subject = new ClaimsIdentity(), - TokenType = JsonWebTokenTypes.Private.LogoutRequest - }); - - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - await _cache.SetStringAsync(Cache.LogoutRequest + context.Request.RequestId, - token, _options.CurrentValue.LogoutRequestCachingPolicy); - - // Create a new GET logout request containing only the request_id parameter. - var address = QueryHelpers.AddQueryString( - uri: request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase + request.Path, - name: Parameters.RequestId, - value: context.Request.RequestId); - - request.HttpContext.Response.Redirect(address); - - // Mark the response as handled to skip the rest of the pipeline. - context.HandleRequest(); - } + // Store the serialized logout request parameters in the distributed cache. + var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor + { + Audience = context.Issuer?.AbsoluteUri, + Claims = context.Request.GetParameters().ToDictionary( + parameter => parameter.Key, + parameter => parameter.Value.Value), + EncryptingCredentials = context.Options.EncryptionCredentials.First(), + Issuer = context.Issuer?.AbsoluteUri, + SigningCredentials = context.Options.SigningCredentials.First(), + Subject = new ClaimsIdentity(), + TokenType = JsonWebTokenTypes.Private.LogoutRequest + }); + + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + await _cache.SetStringAsync(Cache.LogoutRequest + context.Request.RequestId, + token, _options.CurrentValue.LogoutRequestCachingPolicy); + + // Create a new GET logout request containing only the request_id parameter. + var address = QueryHelpers.AddQueryString( + uri: request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase + request.Path, + name: Parameters.RequestId, + value: context.Request.RequestId); + + request.HttpContext.Response.Redirect(address); + + // Mark the response as handled to skip the rest of the pipeline. + context.HandleRequest(); } + } + + /// + /// Contains the logic responsible of removing cached logout requests from the distributed cache. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class RemoveCachedRequest : IOpenIddictServerHandler + { + private readonly IDistributedCache _cache; + + public RemoveCachedRequest() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public RemoveCachedRequest(IDistributedCache cache) + => _cache = cache; /// - /// Contains the logic responsible of removing cached logout requests from the distributed cache. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class RemoveCachedRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyLogoutResponseContext context) { - private readonly IDistributedCache _cache; - - public RemoveCachedRequest() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - - public RemoveCachedRequest(IDistributedCache cache) - => _cache = cache; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyLogoutResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - if (string.IsNullOrEmpty(context.Request?.RequestId)) - { - return default; - } + if (string.IsNullOrEmpty(context.Request?.RequestId)) + { + return default; + } - // Note: the ApplyLogoutResponse event is called for both successful - // and errored logout responses but discrimination is not necessary here, - // as the logout request must be removed from the distributed cache in both cases. + // Note: the ApplyLogoutResponse event is called for both successful + // and errored logout responses but discrimination is not necessary here, + // as the logout request must be removed from the distributed cache in both cases. - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - return new ValueTask(_cache.RemoveAsync(Cache.LogoutRequest + context.Request.RequestId)); - } + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + return new ValueTask(_cache.RemoveAsync(Cache.LogoutRequest + context.Request.RequestId)); } + } + /// + /// Contains the logic responsible of processing logout responses. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessQueryResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing logout responses. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessQueryResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessLocalErrorResponse.Descriptor.Order + 250) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyLogoutResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessLocalErrorResponse.Descriptor.Order + 250) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyLogoutResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) - { - return default; - } + if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) + { + return default; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6151), context.PostLogoutRedirectUri, response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6151), context.PostLogoutRedirectUri, response); - // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters - // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. - // For consistency, multiple parameters with the same name are also supported by this endpoint. + // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters + // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. + // For consistency, multiple parameters with the same name are also supported by this endpoint. #if SUPPORTS_MULTIPLE_VALUES_IN_QUERYHELPERS - var location = QueryHelpers.AddQueryString(context.PostLogoutRedirectUri, - from parameter in context.Response.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select KeyValuePair.Create(parameter.Key, value)); + var location = QueryHelpers.AddQueryString(context.PostLogoutRedirectUri, + from parameter in context.Response.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select KeyValuePair.Create(parameter.Key, value)); #else - var location = context.PostLogoutRedirectUri; - - foreach (var (key, value) in - from parameter in context.Response.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) - { - location = QueryHelpers.AddQueryString(location, key, value); - } + var location = context.PostLogoutRedirectUri; + + foreach (var (key, value) in + from parameter in context.Response.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select (parameter.Key, Value: value)) + { + location = QueryHelpers.AddQueryString(location, key, value); + } #endif - response.Redirect(location); - context.HandleRequest(); + response.Redirect(location); + context.HandleRequest(); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of processing logout responses that should trigger a host redirection. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessHostRedirectionResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing logout responses that should trigger a host redirection. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessHostRedirectionResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessPassthroughErrorResponse.Descriptor.Order + 250) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyLogoutResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessPassthroughErrorResponse.Descriptor.Order + 250) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyLogoutResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + throw new ArgumentNullException(nameof(context)); + } - // Note: this handler only executes if no post_logout_redirect_uri was specified - // and if the response doesn't correspond to an error, that must be handled locally. - if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri) || - !string.IsNullOrEmpty(context.Response.Error)) - { - return default; - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is not null && !string.IsNullOrEmpty(properties.RedirectUri)) - { - response.Redirect(properties.RedirectUri); + // Note: this handler only executes if no post_logout_redirect_uri was specified + // and if the response doesn't correspond to an error, that must be handled locally. + if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri) || + !string.IsNullOrEmpty(context.Response.Error)) + { + return default; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6144)); - context.HandleRequest(); - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is not null && !string.IsNullOrEmpty(properties.RedirectUri)) + { + response.Redirect(properties.RedirectUri); - return default; + context.Logger.LogInformation(SR.GetResourceString(SR.ID6144)); + context.HandleRequest(); } + + return default; } } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Userinfo.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Userinfo.cs index 717a3879..88ce4612 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Userinfo.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Userinfo.cs @@ -8,31 +8,30 @@ using System.Collections.Immutable; using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlerFilters; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +public static partial class OpenIddictServerAspNetCoreHandlers { - public static partial class OpenIddictServerAspNetCoreHandlers + public static class Userinfo { - public static class Userinfo - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Userinfo request extraction: - */ - ExtractGetOrPostRequest.Descriptor, - ExtractAccessToken.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Userinfo request extraction: + */ + ExtractGetOrPostRequest.Descriptor, + ExtractAccessToken.Descriptor, - /* - * Userinfo request handling: - */ - EnablePassthroughMode.Descriptor, + /* + * Userinfo request handling: + */ + EnablePassthroughMode.Descriptor, - /* - * Userinfo response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessChallengeErrorResponse.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Userinfo response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessChallengeErrorResponse.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs index 8f90e9e2..12226084 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs @@ -30,746 +30,729 @@ using static OpenIddict.Server.OpenIddictServerHandlers; using Properties = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class OpenIddictServerAspNetCoreHandlers { - [EditorBrowsable(EditorBrowsableState.Never)] - public static partial class OpenIddictServerAspNetCoreHandlers + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Top-level request processing: + */ + InferEndpointType.Descriptor, + InferIssuerFromHost.Descriptor, + ValidateTransportSecurityRequirement.Descriptor, + + /* + * Challenge processing: + */ + ResolveHostChallengeParameters.Descriptor, + + /* + * Sign-in processing: + */ + ResolveHostSignInParameters.Descriptor, + + /* + * Sign-out processing: + */ + ResolveHostSignOutParameters.Descriptor) + .AddRange(Authentication.DefaultHandlers) + .AddRange(Device.DefaultHandlers) + .AddRange(Discovery.DefaultHandlers) + .AddRange(Exchange.DefaultHandlers) + .AddRange(Introspection.DefaultHandlers) + .AddRange(Revocation.DefaultHandlers) + .AddRange(Session.DefaultHandlers) + .AddRange(Userinfo.DefaultHandlers); + + /// + /// Contains the logic responsible of inferring the endpoint type from the request address. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class InferEndpointType : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Top-level request processing: - */ - InferEndpointType.Descriptor, - InferIssuerFromHost.Descriptor, - ValidateTransportSecurityRequirement.Descriptor, - - /* - * Challenge processing: - */ - ResolveHostChallengeParameters.Descriptor, - - /* - * Sign-in processing: - */ - ResolveHostSignInParameters.Descriptor, - - /* - * Sign-out processing: - */ - ResolveHostSignOutParameters.Descriptor) - .AddRange(Authentication.DefaultHandlers) - .AddRange(Device.DefaultHandlers) - .AddRange(Discovery.DefaultHandlers) - .AddRange(Exchange.DefaultHandlers) - .AddRange(Introspection.DefaultHandlers) - .AddRange(Revocation.DefaultHandlers) - .AddRange(Session.DefaultHandlers) - .AddRange(Userinfo.DefaultHandlers); - /// - /// Contains the logic responsible of inferring the endpoint type from the request address. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class InferEndpointType : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 50_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 50_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - context.EndpointType = - Matches(request, context.Options.AuthorizationEndpointUris) ? OpenIddictServerEndpointType.Authorization : - Matches(request, context.Options.ConfigurationEndpointUris) ? OpenIddictServerEndpointType.Configuration : - Matches(request, context.Options.CryptographyEndpointUris) ? OpenIddictServerEndpointType.Cryptography : - Matches(request, context.Options.DeviceEndpointUris) ? OpenIddictServerEndpointType.Device : - Matches(request, context.Options.IntrospectionEndpointUris) ? OpenIddictServerEndpointType.Introspection : - Matches(request, context.Options.LogoutEndpointUris) ? OpenIddictServerEndpointType.Logout : - Matches(request, context.Options.RevocationEndpointUris) ? OpenIddictServerEndpointType.Revocation : - Matches(request, context.Options.TokenEndpointUris) ? OpenIddictServerEndpointType.Token : - Matches(request, context.Options.UserinfoEndpointUris) ? OpenIddictServerEndpointType.Userinfo : - Matches(request, context.Options.VerificationEndpointUris) ? OpenIddictServerEndpointType.Verification : - OpenIddictServerEndpointType.Unknown; - - if (context.EndpointType != OpenIddictServerEndpointType.Unknown) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6053), context.EndpointType); - } + context.EndpointType = + Matches(request, context.Options.AuthorizationEndpointUris) ? OpenIddictServerEndpointType.Authorization : + Matches(request, context.Options.ConfigurationEndpointUris) ? OpenIddictServerEndpointType.Configuration : + Matches(request, context.Options.CryptographyEndpointUris) ? OpenIddictServerEndpointType.Cryptography : + Matches(request, context.Options.DeviceEndpointUris) ? OpenIddictServerEndpointType.Device : + Matches(request, context.Options.IntrospectionEndpointUris) ? OpenIddictServerEndpointType.Introspection : + Matches(request, context.Options.LogoutEndpointUris) ? OpenIddictServerEndpointType.Logout : + Matches(request, context.Options.RevocationEndpointUris) ? OpenIddictServerEndpointType.Revocation : + Matches(request, context.Options.TokenEndpointUris) ? OpenIddictServerEndpointType.Token : + Matches(request, context.Options.UserinfoEndpointUris) ? OpenIddictServerEndpointType.Userinfo : + Matches(request, context.Options.VerificationEndpointUris) ? OpenIddictServerEndpointType.Verification : + OpenIddictServerEndpointType.Unknown; + + if (context.EndpointType != OpenIddictServerEndpointType.Unknown) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6053), context.EndpointType); + } - return default; + return default; - static bool Matches(HttpRequest request, IReadOnlyList addresses) + static bool Matches(HttpRequest request, IReadOnlyList addresses) + { + for (var index = 0; index < addresses.Count; index++) { - for (var index = 0; index < addresses.Count; index++) + var address = addresses[index]; + if (address.IsAbsoluteUri) { - var address = addresses[index]; - if (address.IsAbsoluteUri) + // If the request host is not available (e.g because HTTP/1.0 was used), ignore absolute URLs. + if (!request.Host.HasValue) { - // If the request host is not available (e.g because HTTP/1.0 was used), ignore absolute URLs. - if (!request.Host.HasValue) - { - continue; - } - - // Create a Uri instance using the request scheme and raw host and compare the two base addresses. - if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host, UriKind.Absolute, out Uri? uri) || - !uri.IsWellFormedOriginalString() || uri.Port != address.Port || - !string.Equals(uri.Scheme, address.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(uri.Host, address.Host, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var path = PathString.FromUriComponent(address); - if (AreEquivalent(path, request.PathBase + request.Path)) - { - return true; - } + continue; } - else if (address.OriginalString.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + // Create a Uri instance using the request scheme and raw host and compare the two base addresses. + if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host, UriKind.Absolute, out Uri? uri) || + !uri.IsWellFormedOriginalString() || uri.Port != address.Port || + !string.Equals(uri.Scheme, address.Scheme, StringComparison.OrdinalIgnoreCase) || + !string.Equals(uri.Host, address.Host, StringComparison.OrdinalIgnoreCase)) { - var path = new PathString(address.OriginalString); - if (AreEquivalent(path, request.Path)) - { - return true; - } + continue; } - } - return false; + var path = PathString.FromUriComponent(address); + if (AreEquivalent(path, request.PathBase + request.Path)) + { + return true; + } + } - // ASP.NET Core's routing system ignores trailing slashes when determining - // whether the request path matches a registered route, which is not the case - // with PathString, that treats /connect/token and /connect/token/ as different - // addresses. To mitigate this inconsistency, a manual check is used here. - static bool AreEquivalent(PathString left, PathString right) - => left.Equals(right, StringComparison.OrdinalIgnoreCase) || - left.Equals(right + "/", StringComparison.OrdinalIgnoreCase) || - right.Equals(left + "/", StringComparison.OrdinalIgnoreCase); + else if (address.OriginalString.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + { + var path = new PathString(address.OriginalString); + if (AreEquivalent(path, request.Path)) + { + return true; + } + } } + + return false; + + // ASP.NET Core's routing system ignores trailing slashes when determining + // whether the request path matches a registered route, which is not the case + // with PathString, that treats /connect/token and /connect/token/ as different + // addresses. To mitigate this inconsistency, a manual check is used here. + static bool AreEquivalent(PathString left, PathString right) + => left.Equals(right, StringComparison.OrdinalIgnoreCase) || + left.Equals(right + "/", StringComparison.OrdinalIgnoreCase) || + right.Equals(left + "/", StringComparison.OrdinalIgnoreCase); } } + } + /// + /// Contains the logic responsible of infering the issuer URL from the HTTP request host and validating it. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class InferIssuerFromHost : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of infering the issuer URL from the HTTP request host and validating it. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class InferIssuerFromHost : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(InferEndpointType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(InferEndpointType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - // Don't require that the request host be present if the request is not handled - // by an OpenIddict endpoint or if an explicit issuer URL was already set. - if (context.Issuer is not null || context.EndpointType == OpenIddictServerEndpointType.Unknown) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (!request.Host.HasValue) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2081(HeaderNames.Host), - uri: SR.FormatID8000(SR.ID2081)); + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - return default; - } + // Don't require that the request host be present if the request is not handled + // by an OpenIddict endpoint or if an explicit issuer URL was already set. + if (context.Issuer is not null || context.EndpointType == OpenIddictServerEndpointType.Unknown) + { + return default; + } - if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase, UriKind.Absolute, out Uri? issuer) || - !issuer.IsWellFormedOriginalString()) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2082(HeaderNames.Host), - uri: SR.FormatID8000(SR.ID2082)); + if (!request.Host.HasValue) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2081(HeaderNames.Host), + uri: SR.FormatID8000(SR.ID2081)); - return default; - } + return default; + } - context.Issuer = issuer; + if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase, UriKind.Absolute, out Uri? issuer) || + !issuer.IsWellFormedOriginalString()) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2082(HeaderNames.Host), + uri: SR.FormatID8000(SR.ID2082)); return default; } + + context.Issuer = issuer; + + return default; } + } + /// + /// Contains the logic responsible of rejecting OpenID Connect requests that don't use transport security. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ValidateTransportSecurityRequirement : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting OpenID Connect requests that don't use transport security. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTransportSecurityRequirement : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(InferEndpointType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(InferEndpointType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Don't require that the host be present if the request is not handled by OpenIddict. - if (context.EndpointType == OpenIddictServerEndpointType.Unknown) - { - return default; - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - // Reject authorization requests sent without transport security. - if (!request.IsHttps) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2083), - uri: SR.FormatID8000(SR.ID2083)); + // Don't require that the host be present if the request is not handled by OpenIddict. + if (context.EndpointType == OpenIddictServerEndpointType.Unknown) + { + return default; + } - return default; - } + // Reject authorization requests sent without transport security. + if (!request.IsHttps) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2083), + uri: SR.FormatID8000(SR.ID2083)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of resolving the additional sign-in parameters stored in the ASP.NET + /// Core authentication properties specified by the application that triggered the sign-in operation. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ResolveHostChallengeParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of resolving the additional sign-in parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the sign-in operation. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ResolveHostChallengeParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessChallengeContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - return default; - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is null) + { + return default; + } - if (properties.Items.TryGetValue(Properties.Error, out string? error) && - !string.IsNullOrEmpty(error)) - { - context.Parameters[Parameters.Error] = error; - } + if (properties.Items.TryGetValue(Properties.Error, out string? error) && + !string.IsNullOrEmpty(error)) + { + context.Parameters[Parameters.Error] = error; + } - if (properties.Items.TryGetValue(Properties.ErrorDescription, out string? description) && - !string.IsNullOrEmpty(description)) - { - context.Parameters[Parameters.ErrorDescription] = description; - } + if (properties.Items.TryGetValue(Properties.ErrorDescription, out string? description) && + !string.IsNullOrEmpty(description)) + { + context.Parameters[Parameters.ErrorDescription] = description; + } - if (properties.Items.TryGetValue(Properties.ErrorUri, out string? uri) && - !string.IsNullOrEmpty(uri)) - { - context.Parameters[Parameters.ErrorUri] = uri; - } + if (properties.Items.TryGetValue(Properties.ErrorUri, out string? uri) && + !string.IsNullOrEmpty(uri)) + { + context.Parameters[Parameters.ErrorUri] = uri; + } - if (properties.Items.TryGetValue(Properties.Scope, out string? scope) && - !string.IsNullOrEmpty(scope)) - { - context.Parameters[Parameters.Scope] = scope; - } + if (properties.Items.TryGetValue(Properties.Scope, out string? scope) && + !string.IsNullOrEmpty(scope)) + { + context.Parameters[Parameters.Scope] = scope; + } - foreach (var parameter in properties.Parameters) + foreach (var parameter in properties.Parameters) + { + context.Parameters[parameter.Key] = parameter.Value switch { - context.Parameters[parameter.Key] = parameter.Value switch - { - OpenIddictParameter value => value, - JsonElement value => new OpenIddictParameter(value), - bool value => new OpenIddictParameter(value), - int value => new OpenIddictParameter(value), - long value => new OpenIddictParameter(value), - string value => new OpenIddictParameter(value), - string[] value => new OpenIddictParameter(value), - - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) - }; - } + OpenIddictParameter value => value, + JsonElement value => new OpenIddictParameter(value), + bool value => new OpenIddictParameter(value), + int value => new OpenIddictParameter(value), + long value => new OpenIddictParameter(value), + string value => new OpenIddictParameter(value), + string[] value => new OpenIddictParameter(value), - return default; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) + }; } + + return default; } + } + /// + /// Contains the logic responsible of resolving the additional sign-in parameters stored in the ASP.NET + /// Core authentication properties specified by the application that triggered the sign-in operation. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ResolveHostSignInParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of resolving the additional sign-in parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the sign-in operation. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ResolveHostSignInParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(AttachSignInParameters.Descriptor.Order - 500) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachSignInParameters.Descriptor.Order - 500) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - return default; - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is null) + { + return default; + } - foreach (var parameter in properties.Parameters) + foreach (var parameter in properties.Parameters) + { + context.Parameters[parameter.Key] = parameter.Value switch { - context.Parameters[parameter.Key] = parameter.Value switch - { - OpenIddictParameter value => value, - JsonElement value => new OpenIddictParameter(value), - bool value => new OpenIddictParameter(value), - int value => new OpenIddictParameter(value), - long value => new OpenIddictParameter(value), - string value => new OpenIddictParameter(value), - string[] value => new OpenIddictParameter(value), - - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) - }; - } + OpenIddictParameter value => value, + JsonElement value => new OpenIddictParameter(value), + bool value => new OpenIddictParameter(value), + int value => new OpenIddictParameter(value), + long value => new OpenIddictParameter(value), + string value => new OpenIddictParameter(value), + string[] value => new OpenIddictParameter(value), - return default; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) + }; } + + return default; } + } + /// + /// Contains the logic responsible of resolving the additional sign-out parameters stored in the ASP.NET + /// Core authentication properties specified by the application that triggered the sign-out operation. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ResolveHostSignOutParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of resolving the additional sign-out parameters stored in the ASP.NET - /// Core authentication properties specified by the application that triggered the sign-out operation. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ResolveHostSignOutParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(AttachSignOutParameters.Descriptor.Order - 500) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignOutContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachSignOutParameters.Descriptor.Order - 500) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignOutContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - return default; - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is null) + { + return default; + } - foreach (var parameter in properties.Parameters) + foreach (var parameter in properties.Parameters) + { + context.Parameters[parameter.Key] = parameter.Value switch { - context.Parameters[parameter.Key] = parameter.Value switch - { - OpenIddictParameter value => value, - JsonElement value => new OpenIddictParameter(value), - bool value => new OpenIddictParameter(value), - int value => new OpenIddictParameter(value), - long value => new OpenIddictParameter(value), - string value => new OpenIddictParameter(value), - string[] value => new OpenIddictParameter(value), - - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) - }; - } + OpenIddictParameter value => value, + JsonElement value => new OpenIddictParameter(value), + bool value => new OpenIddictParameter(value), + int value => new OpenIddictParameter(value), + long value => new OpenIddictParameter(value), + string value => new OpenIddictParameter(value), + string[] value => new OpenIddictParameter(value), - return default; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) + }; } + + return default; } + } + /// + /// Contains the logic responsible of extracting OpenID Connect requests from GET HTTP requests. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ExtractGetRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting OpenID Connect requests from GET HTTP requests. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractGetRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ValidateTransportSecurityRequirement.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ValidateTransportSecurityRequirement.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (HttpMethods.IsGet(request.Method)) - { - context.Transaction.Request = new OpenIddictRequest(request.Query); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - else - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + if (HttpMethods.IsGet(request.Method)) + { + context.Transaction.Request = new OpenIddictRequest(request.Query); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2084), - uri: SR.FormatID8000(SR.ID2084)); + else + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2084), + uri: SR.FormatID8000(SR.ID2084)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of extracting OpenID Connect requests from GET or POST HTTP requests. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ExtractGetOrPostRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting OpenID Connect requests from GET or POST HTTP requests. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractGetOrPostRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ExtractGetRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ExtractGetRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - if (HttpMethods.IsGet(request.Method)) - { - context.Transaction.Request = new OpenIddictRequest(request.Query); - } + if (HttpMethods.IsGet(request.Method)) + { + context.Transaction.Request = new OpenIddictRequest(request.Query); + } - else if (HttpMethods.IsPost(request.Method)) + else if (HttpMethods.IsPost(request.Method)) + { + // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization + if (string.IsNullOrEmpty(request.ContentType)) { - // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization - if (string.IsNullOrEmpty(request.ContentType)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6138), HeaderNames.ContentType); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2081(HeaderNames.ContentType), - uri: SR.FormatID8000(SR.ID2081)); - - return; - } - - // May have media/type; charset=utf-8, allow partial match. - if (!request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6139), HeaderNames.ContentType, request.ContentType); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2082(HeaderNames.ContentType), - uri: SR.FormatID8000(SR.ID2082)); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6138), HeaderNames.ContentType); - return; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2081(HeaderNames.ContentType), + uri: SR.FormatID8000(SR.ID2081)); - context.Transaction.Request = new OpenIddictRequest(await request.ReadFormAsync(request.HttpContext.RequestAborted)); + return; } - else + // May have media/type; charset=utf-8, allow partial match. + if (!request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6139), HeaderNames.ContentType, request.ContentType); context.Reject( error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2084), - uri: SR.FormatID8000(SR.ID2084)); + description: SR.FormatID2082(HeaderNames.ContentType), + uri: SR.FormatID8000(SR.ID2082)); return; } + + context.Transaction.Request = new OpenIddictRequest(await request.ReadFormAsync(request.HttpContext.RequestAborted)); + } + + else + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2084), + uri: SR.FormatID8000(SR.ID2084)); + + return; } } + } + /// + /// Contains the logic responsible of extracting OpenID Connect requests from POST HTTP requests. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ExtractPostRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting OpenID Connect requests from POST HTTP requests. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractPostRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - if (HttpMethods.IsPost(request.Method)) + if (HttpMethods.IsPost(request.Method)) + { + // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization + if (string.IsNullOrEmpty(request.ContentType)) { - // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization - if (string.IsNullOrEmpty(request.ContentType)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6138), HeaderNames.ContentType); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2081(HeaderNames.ContentType), - uri: SR.FormatID8000(SR.ID2081)); - - return; - } - - // May have media/type; charset=utf-8, allow partial match. - if (!request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6139), HeaderNames.ContentType, request.ContentType); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2082(HeaderNames.ContentType), - uri: SR.FormatID8000(SR.ID2082)); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6138), HeaderNames.ContentType); - return; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2081(HeaderNames.ContentType), + uri: SR.FormatID8000(SR.ID2081)); - context.Transaction.Request = new OpenIddictRequest(await request.ReadFormAsync(request.HttpContext.RequestAborted)); + return; } - else + // May have media/type; charset=utf-8, allow partial match. + if (!request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6139), HeaderNames.ContentType, request.ContentType); context.Reject( error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2084), - uri: SR.FormatID8000(SR.ID2084)); + description: SR.FormatID2082(HeaderNames.ContentType), + uri: SR.FormatID8000(SR.ID2082)); return; } + + context.Transaction.Request = new OpenIddictRequest(await request.ReadFormAsync(request.HttpContext.RequestAborted)); + } + + else + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2084), + uri: SR.FormatID8000(SR.ID2084)); + + return; } } + } + /// + /// Contains the logic responsible of extracting client credentials from the standard HTTP Authorization header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ExtractBasicAuthenticationCredentials : IOpenIddictServerHandler + where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting client credentials from the standard HTTP Authorization header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractBasicAuthenticationCredentials : IOpenIddictServerHandler - where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ExtractPostRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ExtractPostRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - string header = request.Headers[HeaderNames.Authorization]; - if (string.IsNullOrEmpty(header) || !header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase)) - { - return default; - } - - // At this point, reject requests that use multiple client authentication methods. - // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information. - if (!string.IsNullOrEmpty(context.Transaction.Request.ClientAssertion) || - !string.IsNullOrEmpty(context.Transaction.Request.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6140)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2087), - uri: SR.FormatID8000(SR.ID2087)); + Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); - return default; - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - try - { - var value = header.Substring("Basic ".Length).Trim(); - var data = Encoding.ASCII.GetString(Convert.FromBase64String(value)); + string header = request.Headers[HeaderNames.Authorization]; + if (string.IsNullOrEmpty(header) || !header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase)) + { + return default; + } - var index = data.IndexOf(':'); - if (index < 0) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2055), - uri: SR.FormatID8000(SR.ID2055)); + // At this point, reject requests that use multiple client authentication methods. + // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information. + if (!string.IsNullOrEmpty(context.Transaction.Request.ClientAssertion) || + !string.IsNullOrEmpty(context.Transaction.Request.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6140)); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2087), + uri: SR.FormatID8000(SR.ID2087)); - // Attach the basic authentication credentials to the request message. - context.Transaction.Request.ClientId = UnescapeDataString(data.Substring(0, index)); - context.Transaction.Request.ClientSecret = UnescapeDataString(data.Substring(index + 1)); + return default; + } - return default; - } + try + { + var value = header.Substring("Basic ".Length).Trim(); + var data = Encoding.ASCII.GetString(Convert.FromBase64String(value)); - catch + var index = data.IndexOf(':'); + if (index < 0) { context.Reject( error: Errors.InvalidRequest, @@ -779,641 +762,657 @@ namespace OpenIddict.Server.AspNetCore return default; } - static string? UnescapeDataString(string data) - { - if (string.IsNullOrEmpty(data)) - { - return null; - } + // Attach the basic authentication credentials to the request message. + context.Transaction.Request.ClientId = UnescapeDataString(data.Substring(0, index)); + context.Transaction.Request.ClientSecret = UnescapeDataString(data.Substring(index + 1)); + + return default; + } + + catch + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2055), + uri: SR.FormatID8000(SR.ID2055)); - return Uri.UnescapeDataString(data.Replace("+", "%20")); + return default; + } + + static string? UnescapeDataString(string data) + { + if (string.IsNullOrEmpty(data)) + { + return null; } + + return Uri.UnescapeDataString(data.Replace("+", "%20")); } } + } + /// + /// Contains the logic responsible of extracting an access token from the standard HTTP Authorization header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ExtractAccessToken : IOpenIddictServerHandler + where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting an access token from the standard HTTP Authorization header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractAccessToken : IOpenIddictServerHandler - where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ExtractBasicAuthenticationCredentials.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ExtractBasicAuthenticationCredentials.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - string header = request.Headers[HeaderNames.Authorization]; - if (string.IsNullOrEmpty(header) || !header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) - { - return default; - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - // Attach the access token to the request message. - context.Transaction.Request.AccessToken = header.Substring("Bearer ".Length); + Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); + string header = request.Headers[HeaderNames.Authorization]; + if (string.IsNullOrEmpty(header) || !header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { return default; } + + // Attach the access token to the request message. + context.Transaction.Request.AccessToken = header.Substring("Bearer ".Length); + + return default; } + } + /// + /// Contains the logic responsible of enabling the pass-through mode for the received request. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class EnablePassthroughMode : IOpenIddictServerHandler + where TContext : BaseRequestContext + where TFilter : IOpenIddictServerHandlerFilter + { /// - /// Contains the logic responsible of enabling the pass-through mode for the received request. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class EnablePassthroughMode : IOpenIddictServerHandler - where TContext : BaseRequestContext - where TFilter : IOpenIddictServerHandlerFilter + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.SkipRequest(); + context.SkipRequest(); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching an appropriate HTTP status code. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class AttachHttpResponseCode : IOpenIddictServerHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of attaching an appropriate HTTP status code. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachHttpResponseCode : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // When client authentication is made using basic authentication, the authorization server MUST return - // a 401 response with a valid WWW-Authenticate header containing the Basic scheme and a non-empty realm. - // A similar error MAY be returned even when basic authentication is not used and MUST also be returned - // when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme. - // To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors - // and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials - // were specified in the request form instead of the HTTP headers, as allowed by the specification. - response.StatusCode = context.Transaction.Response.Error switch - { - null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects). + // When client authentication is made using basic authentication, the authorization server MUST return + // a 401 response with a valid WWW-Authenticate header containing the Basic scheme and a non-empty realm. + // A similar error MAY be returned even when basic authentication is not used and MUST also be returned + // when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme. + // To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors + // and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials + // were specified in the request form instead of the HTTP headers, as allowed by the specification. + response.StatusCode = context.Transaction.Response.Error switch + { + null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects). - Errors.InvalidClient or Errors.InvalidToken or Errors.MissingToken => 401, + Errors.InvalidClient or Errors.InvalidToken or Errors.MissingToken => 401, - Errors.InsufficientAccess or Errors.InsufficientScope => 403, + Errors.InsufficientAccess or Errors.InsufficientScope => 403, - _ => 400 - }; + _ => 400 + }; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the appropriate HTTP response cache headers. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class AttachCacheControlHeader : IOpenIddictServerHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of attaching the appropriate HTTP response cache headers. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachCacheControlHeader : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachHttpResponseCode.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachHttpResponseCode.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - // Prevent the response from being cached. - response.Headers[HeaderNames.CacheControl] = "no-store"; - response.Headers[HeaderNames.Pragma] = "no-cache"; - response.Headers[HeaderNames.Expires] = "Thu, 01 Jan 1970 00:00:00 GMT"; + // Prevent the response from being cached. + response.Headers[HeaderNames.CacheControl] = "no-store"; + response.Headers[HeaderNames.Pragma] = "no-cache"; + response.Headers[HeaderNames.Expires] = "Thu, 01 Jan 1970 00:00:00 GMT"; - return default; - } + return default; } + } + + /// + /// Contains the logic responsible of attaching errors details to the WWW-Authenticate header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class AttachWwwAuthenticateHeader : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOptionsMonitor _options; + + public AttachWwwAuthenticateHeader(IOptionsMonitor options) + => _options = options; /// - /// Contains the logic responsible of attaching errors details to the WWW-Authenticate header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachWwwAuthenticateHeader : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachCacheControlHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - private readonly IOptionsMonitor _options; - - public AttachWwwAuthenticateHeader(IOptionsMonitor options) - => _options = options; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachCacheControlHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // When client authentication is made using basic authentication, the authorization server MUST return - // a 401 response with a valid WWW-Authenticate header containing the HTTP Basic authentication scheme. - // A similar error MAY be returned even when basic authentication is not used and MUST also be returned - // when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme. - // To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors - // and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials - // were specified in the request form instead of the HTTP headers, as allowed by the specification. - var scheme = context.Transaction.Response.Error switch - { - Errors.InvalidClient => Schemes.Basic, + // When client authentication is made using basic authentication, the authorization server MUST return + // a 401 response with a valid WWW-Authenticate header containing the HTTP Basic authentication scheme. + // A similar error MAY be returned even when basic authentication is not used and MUST also be returned + // when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme. + // To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors + // and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials + // were specified in the request form instead of the HTTP headers, as allowed by the specification. + var scheme = context.Transaction.Response.Error switch + { + Errors.InvalidClient => Schemes.Basic, - Errors.InvalidToken or - Errors.MissingToken or - Errors.InsufficientAccess or - Errors.InsufficientScope => Schemes.Bearer, + Errors.InvalidToken or + Errors.MissingToken or + Errors.InsufficientAccess or + Errors.InsufficientScope => Schemes.Bearer, - _ => null - }; + _ => null + }; - if (string.IsNullOrEmpty(scheme)) - { - return default; - } + if (string.IsNullOrEmpty(scheme)) + { + return default; + } - var parameters = new Dictionary(StringComparer.Ordinal); + var parameters = new Dictionary(StringComparer.Ordinal); - // If a realm was configured in the options, attach it to the parameters. - if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) - { - parameters[Parameters.Realm] = _options.CurrentValue.Realm; - } + // If a realm was configured in the options, attach it to the parameters. + if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) + { + parameters[Parameters.Realm] = _options.CurrentValue.Realm; + } - foreach (var parameter in context.Transaction.Response.GetParameters()) + foreach (var parameter in context.Transaction.Response.GetParameters()) + { + // Note: the error details are only included if the error was not caused by a missing token, as recommended + // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. + if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && + (string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || + string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || + string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) { - // Note: the error details are only included if the error was not caused by a missing token, as recommended - // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. - if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && - (string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || - string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || - string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) - { - continue; - } - - // Ignore values that can't be represented as unique strings. - var value = (string?) parameter.Value; - if (string.IsNullOrEmpty(value)) - { - continue; - } - - parameters[parameter.Key] = value; + continue; } - var builder = new StringBuilder(scheme); - - foreach (var parameter in parameters) + // Ignore values that can't be represented as unique strings. + var value = (string?) parameter.Value; + if (string.IsNullOrEmpty(value)) { - builder.Append(' '); - builder.Append(parameter.Key); - builder.Append('='); - builder.Append('"'); - builder.Append(parameter.Value.Replace("\"", "\\\"")); - builder.Append('"'); - builder.Append(','); + continue; } - // If the WWW-Authenticate header ends with a comma, remove it. - if (builder[builder.Length - 1] == ',') - { - builder.Remove(builder.Length - 1, 1); - } + parameters[parameter.Key] = value; + } - response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString()); + var builder = new StringBuilder(scheme); - return default; + foreach (var parameter in parameters) + { + builder.Append(' '); + builder.Append(parameter.Key); + builder.Append('='); + builder.Append('"'); + builder.Append(parameter.Value.Replace("\"", "\\\"")); + builder.Append('"'); + builder.Append(','); } + + // If the WWW-Authenticate header ends with a comma, remove it. + if (builder[builder.Length - 1] == ',') + { + builder.Remove(builder.Length - 1, 1); + } + + response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString()); + + return default; } + } + /// + /// Contains the logic responsible of processing challenge responses that contain a WWW-Authenticate header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessChallengeErrorResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing challenge responses that contain a WWW-Authenticate header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessChallengeErrorResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachWwwAuthenticateHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachWwwAuthenticateHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - // If the response doesn't contain a WWW-Authenticate header, don't return an empty response. - if (!response.Headers.ContainsKey(HeaderNames.WWWAuthenticate)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6141), context.Transaction.Response); - context.HandleRequest(); + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } + // If the response doesn't contain a WWW-Authenticate header, don't return an empty response. + if (!response.Headers.ContainsKey(HeaderNames.WWWAuthenticate)) + { return default; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6141), context.Transaction.Response); + context.HandleRequest(); + + return default; } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessJsonResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessJsonResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ProcessChallengeErrorResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ProcessChallengeErrorResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response); - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = true - }); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = true + }); - context.Transaction.Response.WriteTo(writer); - writer.Flush(); + context.Transaction.Response.WriteTo(writer); + writer.Flush(); - response.ContentLength = stream.Length; - response.ContentType = "application/json;charset=UTF-8"; + response.ContentLength = stream.Length; + response.ContentType = "application/json;charset=UTF-8"; - stream.Seek(offset: 0, loc: SeekOrigin.Begin); - await stream.CopyToAsync(response.Body, 4096, response.HttpContext.RequestAborted); + stream.Seek(offset: 0, loc: SeekOrigin.Begin); + await stream.CopyToAsync(response.Body, 4096, response.HttpContext.RequestAborted); - context.HandleRequest(); - } + context.HandleRequest(); } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses that must be handled by another + /// middleware in the pipeline at a later stage (e.g an ASP.NET Core MVC action or a NancyFX module). + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessPassthroughErrorResponse : IOpenIddictServerHandler + where TContext : BaseRequestContext + where TFilter : IOpenIddictServerHandlerFilter + { /// - /// Contains the logic responsible of processing OpenID Connect responses that must be handled by another - /// middleware in the pipeline at a later stage (e.g an ASP.NET Core MVC action or a NancyFX module). - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessPassthroughErrorResponse : IOpenIddictServerHandler - where TContext : BaseRequestContext - where TFilter : IOpenIddictServerHandlerFilter + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ProcessJsonResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ProcessJsonResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (string.IsNullOrEmpty(context.Transaction.Response.Error)) - { - return default; - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - context.SkipRequest(); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + if (string.IsNullOrEmpty(context.Transaction.Response.Error)) + { return default; } + + context.SkipRequest(); + + return default; } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses handled by the status code pages middleware. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessStatusCodePagesErrorResponse : IOpenIddictServerHandler + where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing OpenID Connect responses handled by the status code pages middleware. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessStatusCodePagesErrorResponse : IOpenIddictServerHandler - where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ProcessPassthroughErrorResponse>.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ProcessPassthroughErrorResponse>.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (string.IsNullOrEmpty(context.Transaction.Response.Error)) - { - return default; - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - // Determine if the status code pages middleware has been enabled for this request. - // If it was not registered or enabled, let the default OpenIddict server handlers render - // a default error page instead of delegating the rendering to the status code middleware. - var feature = response.HttpContext.Features.Get(); - if (feature is null || !feature.Enabled) - { - return default; - } + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // Mark the request as fully handled to prevent the other OpenIddict server handlers - // from displaying the default error page and to allow the status code pages middleware - // to rewrite the response using the logic defined by the developer when registering it. - context.HandleRequest(); + if (string.IsNullOrEmpty(context.Transaction.Response.Error)) + { + return default; + } + // Determine if the status code pages middleware has been enabled for this request. + // If it was not registered or enabled, let the default OpenIddict server handlers render + // a default error page instead of delegating the rendering to the status code middleware. + var feature = response.HttpContext.Features.Get(); + if (feature is null || !feature.Enabled) + { return default; } + + // Mark the request as fully handled to prevent the other OpenIddict server handlers + // from displaying the default error page and to allow the status code pages middleware + // to rewrite the response using the logic defined by the developer when registering it. + context.HandleRequest(); + + return default; } + } + /// + /// Contains the logic responsible of processing context responses that must be returned as plain-text. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessLocalErrorResponse : IOpenIddictServerHandler + where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing context responses that must be returned as plain-text. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessLocalErrorResponse : IOpenIddictServerHandler - where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ProcessStatusCodePagesErrorResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ProcessStatusCodePagesErrorResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - if (string.IsNullOrEmpty(context.Transaction.Response.Error)) - { - return; - } + if (string.IsNullOrEmpty(context.Transaction.Response.Error)) + { + return; + } - // Don't return the state originally sent by the client application. - context.Transaction.Response.State = null; + // Don't return the state originally sent by the client application. + context.Transaction.Response.State = null; - context.Logger.LogInformation(SR.GetResourceString(SR.ID6143), context.Transaction.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6143), context.Transaction.Response); - using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream); + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); - foreach (var parameter in context.Transaction.Response.GetParameters()) + foreach (var parameter in context.Transaction.Response.GetParameters()) + { + // Ignore null or empty parameters, including JSON + // objects that can't be represented as strings. + var value = (string?) parameter.Value; + if (string.IsNullOrEmpty(value)) { - // Ignore null or empty parameters, including JSON - // objects that can't be represented as strings. - var value = (string?) parameter.Value; - if (string.IsNullOrEmpty(value)) - { - continue; - } - - writer.Write(parameter.Key); - writer.Write(':'); - writer.Write(value); - writer.WriteLine(); + continue; } - writer.Flush(); + writer.Write(parameter.Key); + writer.Write(':'); + writer.Write(value); + writer.WriteLine(); + } - response.ContentLength = stream.Length; - response.ContentType = "text/plain;charset=UTF-8"; + writer.Flush(); - stream.Seek(offset: 0, loc: SeekOrigin.Begin); - await stream.CopyToAsync(response.Body, 4096, response.HttpContext.RequestAborted); + response.ContentLength = stream.Length; + response.ContentType = "text/plain;charset=UTF-8"; - context.HandleRequest(); - } + stream.Seek(offset: 0, loc: SeekOrigin.Begin); + await stream.CopyToAsync(response.Body, 4096, response.HttpContext.RequestAborted); + + context.HandleRequest(); } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses that don't specify any parameter. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessEmptyResponse : IOpenIddictServerHandler + where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing OpenID Connect responses that don't specify any parameter. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessEmptyResponse : IOpenIddictServerHandler - where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6145)); - context.HandleRequest(); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6145)); + context.HandleRequest(); - return default; - } + return default; } } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHelpers.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHelpers.cs index 02bb2e40..271a6173 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHelpers.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHelpers.cs @@ -11,81 +11,80 @@ using OpenIddict.Server; using OpenIddict.Server.AspNetCore; using static OpenIddict.Server.OpenIddictServerEvents; -namespace Microsoft.AspNetCore +namespace Microsoft.AspNetCore; + +/// +/// Exposes companion extensions for the OpenIddict/ASP.NET Core integration. +/// +public static class OpenIddictServerAspNetCoreHelpers { /// - /// Exposes companion extensions for the OpenIddict/ASP.NET Core integration. + /// Retrieves the instance stored in the properties. /// - public static class OpenIddictServerAspNetCoreHelpers + /// The transaction instance. + /// The instance or null if it couldn't be found. + public static HttpRequest? GetHttpRequest(this OpenIddictServerTransaction transaction) { - /// - /// Retrieves the instance stored in the properties. - /// - /// The transaction instance. - /// The instance or null if it couldn't be found. - public static HttpRequest? GetHttpRequest(this OpenIddictServerTransaction transaction) + if (transaction is null) { - if (transaction is null) - { - throw new ArgumentNullException(nameof(transaction)); - } - - if (!transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)) - { - return null; - } - - if (property is WeakReference reference && reference.TryGetTarget(out HttpRequest? request)) - { - return request; - } + throw new ArgumentNullException(nameof(transaction)); + } + if (!transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)) + { return null; } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The . - public static OpenIddictServerEndpointType GetOpenIddictServerEndpointType(this HttpContext context) + if (property is WeakReference reference && reference.TryGetTarget(out HttpRequest? request)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return context.Features.Get()?.Transaction?.EndpointType ?? default; + return request; } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The instance or null if it couldn't be found. - public static OpenIddictRequest? GetOpenIddictServerRequest(this HttpContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return null; + } - return context.Features.Get()?.Transaction?.Request; + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The . + public static OpenIddictServerEndpointType GetOpenIddictServerEndpointType(this HttpContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The instance or null if it couldn't be found. - public static OpenIddictResponse? GetOpenIddictServerResponse(this HttpContext context) + return context.Features.Get()?.Transaction?.EndpointType ?? default; + } + + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The instance or null if it couldn't be found. + public static OpenIddictRequest? GetOpenIddictServerRequest(this HttpContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } + + return context.Features.Get()?.Transaction?.Request; + } - return context.Features.Get()?.Transaction?.Response; + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The instance or null if it couldn't be found. + public static OpenIddictResponse? GetOpenIddictServerResponse(this HttpContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); } + + return context.Features.Get()?.Transaction?.Response; } } diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreOptions.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreOptions.cs index 8735139b..d235a124 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreOptions.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreOptions.cs @@ -10,113 +10,112 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; -namespace OpenIddict.Server.AspNetCore +namespace OpenIddict.Server.AspNetCore; + +/// +/// Provides various settings needed to configure the OpenIddict ASP.NET Core server integration. +/// +public class OpenIddictServerAspNetCoreOptions : AuthenticationSchemeOptions { /// - /// Provides various settings needed to configure the OpenIddict ASP.NET Core server integration. + /// Gets or sets a boolean indicating whether incoming requests arriving on insecure endpoints should be rejected. + /// By default, this property is set to false to help mitigate man-in-the-middle attacks. /// - public class OpenIddictServerAspNetCoreOptions : AuthenticationSchemeOptions - { - /// - /// Gets or sets a boolean indicating whether incoming requests arriving on insecure endpoints should be rejected. - /// By default, this property is set to false to help mitigate man-in-the-middle attacks. - /// - public bool DisableTransportSecurityRequirement { get; set; } + public bool DisableTransportSecurityRequirement { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the authorization endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableAuthorizationEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the authorization endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableAuthorizationEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether OpenIddict should allow the rest of the request processing pipeline - /// to be invoked when returning an error from the interactive authorization and logout endpoints. - /// When this option is enabled, special logic must be added to these actions to handle errors, that can be - /// retrieved using . - /// - /// - /// Important: the error pass-through mode cannot be used when the status code pages integration is enabled. - /// - public bool EnableErrorPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether OpenIddict should allow the rest of the request processing pipeline + /// to be invoked when returning an error from the interactive authorization and logout endpoints. + /// When this option is enabled, special logic must be added to these actions to handle errors, that can be + /// retrieved using . + /// + /// + /// Important: the error pass-through mode cannot be used when the status code pages integration is enabled. + /// + public bool EnableErrorPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the logout endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableLogoutEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the logout endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableLogoutEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the token endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableTokenEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the token endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableTokenEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the userinfo endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableUserinfoEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the userinfo endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableUserinfoEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the user verification endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableVerificationEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the user verification endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableVerificationEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether requests received by the authorization endpoint - /// should be cached. When enabled, authorization requests are automatically stored - /// in the distributed cache, which allows flowing large payloads across requests. - /// Enabling this option is recommended when using external authentication providers - /// or when large GET or POST OpenID Connect authorization requests support is required. - /// - public bool EnableAuthorizationRequestCaching { get; set; } + /// + /// Gets or sets a boolean indicating whether requests received by the authorization endpoint + /// should be cached. When enabled, authorization requests are automatically stored + /// in the distributed cache, which allows flowing large payloads across requests. + /// Enabling this option is recommended when using external authentication providers + /// or when large GET or POST OpenID Connect authorization requests support is required. + /// + public bool EnableAuthorizationRequestCaching { get; set; } - /// - /// Gets or sets a boolean indicating whether requests received by the logout endpoint should be cached. - /// When enabled, authorization requests are automatically stored in the distributed cache. - /// - public bool EnableLogoutRequestCaching { get; set; } + /// + /// Gets or sets a boolean indicating whether requests received by the logout endpoint should be cached. + /// When enabled, authorization requests are automatically stored in the distributed cache. + /// + public bool EnableLogoutRequestCaching { get; set; } - /// - /// Gets or sets a boolean indicating whether integration with the status code pages - /// middleware should be enabled or not. Once enabled, errors generated by the OpenIddict - /// interactive endpoints (e.g authorization or logout) can be handled by ASP.NET Core. - /// - public bool EnableStatusCodePagesIntegration { get; set; } + /// + /// Gets or sets a boolean indicating whether integration with the status code pages + /// middleware should be enabled or not. Once enabled, errors generated by the OpenIddict + /// interactive endpoints (e.g authorization or logout) can be handled by ASP.NET Core. + /// + public bool EnableStatusCodePagesIntegration { get; set; } - /// - /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. - /// - public string? Realm { get; set; } + /// + /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. + /// + public string? Realm { get; set; } - /// - /// Gets or sets the caching policy used by the authorization endpoint. - /// - public DistributedCacheEntryOptions AuthorizationRequestCachingPolicy { get; set; } = new() - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), - SlidingExpiration = TimeSpan.FromMinutes(30) - }; + /// + /// Gets or sets the caching policy used by the authorization endpoint. + /// + public DistributedCacheEntryOptions AuthorizationRequestCachingPolicy { get; set; } = new() + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), + SlidingExpiration = TimeSpan.FromMinutes(30) + }; - /// - /// Gets or sets the caching policy used by the logout endpoint. - /// - public DistributedCacheEntryOptions LogoutRequestCachingPolicy { get; set; } = new() - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), - SlidingExpiration = TimeSpan.FromMinutes(30) - }; - } + /// + /// Gets or sets the caching policy used by the logout endpoint. + /// + public DistributedCacheEntryOptions LogoutRequestCachingPolicy { get; set; } = new() + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), + SlidingExpiration = TimeSpan.FromMinutes(30) + }; } diff --git a/src/OpenIddict.Server.DataProtection/IOpenIddictServerDataProtectionFormatter.cs b/src/OpenIddict.Server.DataProtection/IOpenIddictServerDataProtectionFormatter.cs index 91425c16..fc1d7921 100644 --- a/src/OpenIddict.Server.DataProtection/IOpenIddictServerDataProtectionFormatter.cs +++ b/src/OpenIddict.Server.DataProtection/IOpenIddictServerDataProtectionFormatter.cs @@ -7,11 +7,10 @@ using System.IO; using System.Security.Claims; -namespace OpenIddict.Server.DataProtection +namespace OpenIddict.Server.DataProtection; + +public interface IOpenIddictServerDataProtectionFormatter { - public interface IOpenIddictServerDataProtectionFormatter - { - ClaimsPrincipal ReadToken(BinaryReader reader); - void WriteToken(BinaryWriter writer, ClaimsPrincipal principal); - } -} \ No newline at end of file + ClaimsPrincipal ReadToken(BinaryReader reader); + void WriteToken(BinaryWriter writer, ClaimsPrincipal principal); +} diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs index b51fbd6f..e8c91d35 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs @@ -9,121 +9,120 @@ using System.ComponentModel; using Microsoft.AspNetCore.DataProtection; using OpenIddict.Server.DataProtection; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the +/// OpenIddict ASP.NET Core Data Protection integration. +/// +public class OpenIddictServerDataProtectionBuilder { /// - /// Exposes the necessary methods required to configure the - /// OpenIddict ASP.NET Core Data Protection integration. + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictServerDataProtectionBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict server ASP.NET Core Data Protection configuration. /// - public class OpenIddictServerDataProtectionBuilder + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictServerDataProtectionBuilder Configure(Action configuration) { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictServerDataProtectionBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Amends the default OpenIddict server ASP.NET Core Data Protection configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictServerDataProtectionBuilder Configure(Action configuration) + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Configures OpenIddict to use a specific data protection provider - /// instead of relying on the default instance provided by the DI container. - /// - /// The data protection provider used to create token protectors. - /// The . - public OpenIddictServerDataProtectionBuilder UseDataProtectionProvider(IDataProtectionProvider provider) + /// + /// Configures OpenIddict to use a specific data protection provider + /// instead of relying on the default instance provided by the DI container. + /// + /// The data protection provider used to create token protectors. + /// The . + public OpenIddictServerDataProtectionBuilder UseDataProtectionProvider(IDataProtectionProvider provider) + { + if (provider is null) { - if (provider is null) - { - throw new ArgumentNullException(nameof(provider)); - } - - return Configure(options => options.DataProtectionProvider = provider); + throw new ArgumentNullException(nameof(provider)); } - /// - /// Configures OpenIddict to use a specific formatter instead of relying on the default instance. - /// - /// The formatter used to read and write tokens. - /// The . - public OpenIddictServerDataProtectionBuilder UseFormatter(IOpenIddictServerDataProtectionFormatter formatter) - { - if (formatter is null) - { - throw new ArgumentNullException(nameof(formatter)); - } + return Configure(options => options.DataProtectionProvider = provider); + } - return Configure(options => options.Formatter = formatter); + /// + /// Configures OpenIddict to use a specific formatter instead of relying on the default instance. + /// + /// The formatter used to read and write tokens. + /// The . + public OpenIddictServerDataProtectionBuilder UseFormatter(IOpenIddictServerDataProtectionFormatter formatter) + { + if (formatter is null) + { + throw new ArgumentNullException(nameof(formatter)); } - /// - /// Configures OpenIddict to use the default token format (JWT) when issuing new access tokens. - /// - /// The . - public OpenIddictServerDataProtectionBuilder PreferDefaultAccessTokenFormat() - => Configure(options => options.PreferDefaultAccessTokenFormat = true); - - /// - /// Configures OpenIddict to use the default token format (JWT) when issuing new authorization codes. - /// - /// The . - public OpenIddictServerDataProtectionBuilder PreferDefaultAuthorizationCodeFormat() - => Configure(options => options.PreferDefaultAuthorizationCodeFormat = true); - - /// - /// Configures OpenIddict to use the default token format (JWT) when issuing new device codes. - /// - /// The . - public OpenIddictServerDataProtectionBuilder PreferDefaultDeviceCodeFormat() - => Configure(options => options.PreferDefaultDeviceCodeFormat = true); - - /// - /// Configures OpenIddict to use the default token format (JWT) when issuing new refresh tokens. - /// - /// The . - public OpenIddictServerDataProtectionBuilder PreferDefaultRefreshTokenFormat() - => Configure(options => options.PreferDefaultRefreshTokenFormat = true); - - /// - /// Configures OpenIddict to use the default token format (JWT) when issuing new user codes. - /// - /// The . - public OpenIddictServerDataProtectionBuilder PreferDefaultUserCodeFormat() - => Configure(options => options.PreferDefaultUserCodeFormat = true); - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); + return Configure(options => options.Formatter = formatter); } + + /// + /// Configures OpenIddict to use the default token format (JWT) when issuing new access tokens. + /// + /// The . + public OpenIddictServerDataProtectionBuilder PreferDefaultAccessTokenFormat() + => Configure(options => options.PreferDefaultAccessTokenFormat = true); + + /// + /// Configures OpenIddict to use the default token format (JWT) when issuing new authorization codes. + /// + /// The . + public OpenIddictServerDataProtectionBuilder PreferDefaultAuthorizationCodeFormat() + => Configure(options => options.PreferDefaultAuthorizationCodeFormat = true); + + /// + /// Configures OpenIddict to use the default token format (JWT) when issuing new device codes. + /// + /// The . + public OpenIddictServerDataProtectionBuilder PreferDefaultDeviceCodeFormat() + => Configure(options => options.PreferDefaultDeviceCodeFormat = true); + + /// + /// Configures OpenIddict to use the default token format (JWT) when issuing new refresh tokens. + /// + /// The . + public OpenIddictServerDataProtectionBuilder PreferDefaultRefreshTokenFormat() + => Configure(options => options.PreferDefaultRefreshTokenFormat = true); + + /// + /// Configures OpenIddict to use the default token format (JWT) when issuing new user codes. + /// + /// The . + public OpenIddictServerDataProtectionBuilder PreferDefaultUserCodeFormat() + => Configure(options => options.PreferDefaultUserCodeFormat = true); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs index 8f364a8e..3ba70992 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs @@ -8,48 +8,47 @@ using System; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Options; -namespace OpenIddict.Server.DataProtection +namespace OpenIddict.Server.DataProtection; + +/// +/// Contains the methods required to ensure that the OpenIddict ASP.NET Core Data Protection configuration is valid. +/// +public class OpenIddictServerDataProtectionConfiguration : IConfigureOptions, + IPostConfigureOptions { + private readonly IDataProtectionProvider _dataProtectionProvider; + /// - /// Contains the methods required to ensure that the OpenIddict ASP.NET Core Data Protection configuration is valid. + /// Creates a new instance of the class. /// - public class OpenIddictServerDataProtectionConfiguration : IConfigureOptions, - IPostConfigureOptions - { - private readonly IDataProtectionProvider _dataProtectionProvider; - - /// - /// Creates a new instance of the class. - /// - /// The ASP.NET Core Data Protection provider. - public OpenIddictServerDataProtectionConfiguration(IDataProtectionProvider dataProtectionProvider) - => _dataProtectionProvider = dataProtectionProvider; + /// The ASP.NET Core Data Protection provider. + public OpenIddictServerDataProtectionConfiguration(IDataProtectionProvider dataProtectionProvider) + => _dataProtectionProvider = dataProtectionProvider; - public void Configure(OpenIddictServerOptions options) + public void Configure(OpenIddictServerOptions options) + { + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - // Register the built-in event handlers used by the OpenIddict Data Protection server components. - options.Handlers.AddRange(OpenIddictServerDataProtectionHandlers.DefaultHandlers); + throw new ArgumentNullException(nameof(options)); } - /// - /// Populates the default OpenIddict ASP.NET Core Data Protection server options - /// and ensures that the configuration is in a consistent and valid state. - /// - /// The name of the options instance to configure, if applicable. - /// The options instance to initialize. - public void PostConfigure(string name, OpenIddictServerDataProtectionOptions options) - { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + // Register the built-in event handlers used by the OpenIddict Data Protection server components. + options.Handlers.AddRange(OpenIddictServerDataProtectionHandlers.DefaultHandlers); + } - options.DataProtectionProvider ??= _dataProtectionProvider; + /// + /// Populates the default OpenIddict ASP.NET Core Data Protection server options + /// and ensures that the configuration is in a consistent and valid state. + /// + /// The name of the options instance to configure, if applicable. + /// The options instance to initialize. + public void PostConfigure(string name, OpenIddictServerDataProtectionOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); } + + options.DataProtectionProvider ??= _dataProtectionProvider; } } diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs index c37539d8..1fd777ed 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs @@ -4,59 +4,58 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Server.DataProtection +namespace OpenIddict.Server.DataProtection; + +public static class OpenIddictServerDataProtectionConstants { - public static class OpenIddictServerDataProtectionConstants + public static class Properties { - public static class Properties + public const string AccessTokenLifetime = ".access_token_lifetime"; + public const string AuthorizationCodeLifetime = ".authorization_code_lifetime"; + public const string Audiences = ".audiences"; + public const string CodeChallenge = ".code_challenge"; + public const string CodeChallengeMethod = ".code_challenge_method"; + public const string DataProtector = ".data_protector"; + public const string DeviceCodeId = ".device_code_id"; + public const string DeviceCodeLifetime = ".device_code_lifetime"; + public const string Expires = ".expires"; + public const string IdentityTokenLifetime = ".identity_token_lifetime"; + public const string InternalAuthorizationId = ".internal_authorization_id"; + public const string InternalTokenId = ".internal_token_id"; + public const string Issued = ".issued"; + public const string Nonce = ".nonce"; + public const string OriginalRedirectUri = ".original_redirect_uri"; + public const string Presenters = ".presenters"; + public const string RefreshTokenLifetime = ".refresh_token_lifetime"; + public const string Resources = ".resources"; + public const string Scopes = ".scopes"; + public const string UserCodeLifetime = ".user_code_lifetime"; + } + + public static class Purposes + { + public static class Features { - public const string AccessTokenLifetime = ".access_token_lifetime"; - public const string AuthorizationCodeLifetime = ".authorization_code_lifetime"; - public const string Audiences = ".audiences"; - public const string CodeChallenge = ".code_challenge"; - public const string CodeChallengeMethod = ".code_challenge_method"; - public const string DataProtector = ".data_protector"; - public const string DeviceCodeId = ".device_code_id"; - public const string DeviceCodeLifetime = ".device_code_lifetime"; - public const string Expires = ".expires"; - public const string IdentityTokenLifetime = ".identity_token_lifetime"; - public const string InternalAuthorizationId = ".internal_authorization_id"; - public const string InternalTokenId = ".internal_token_id"; - public const string Issued = ".issued"; - public const string Nonce = ".nonce"; - public const string OriginalRedirectUri = ".original_redirect_uri"; - public const string Presenters = ".presenters"; - public const string RefreshTokenLifetime = ".refresh_token_lifetime"; - public const string Resources = ".resources"; - public const string Scopes = ".scopes"; - public const string UserCodeLifetime = ".user_code_lifetime"; + public const string ReferenceTokens = "UseReferenceTokens"; } - public static class Purposes + public static class Formats { - public static class Features - { - public const string ReferenceTokens = "UseReferenceTokens"; - } - - public static class Formats - { - public const string AccessToken = "AccessTokenFormat"; - public const string AuthorizationCode = "AuthorizationCodeFormat"; - public const string DeviceCode = "DeviceCodeFormat"; - public const string RefreshToken = "RefreshTokenFormat"; - public const string UserCode = "UserCodeFormat"; - } + public const string AccessToken = "AccessTokenFormat"; + public const string AuthorizationCode = "AuthorizationCodeFormat"; + public const string DeviceCode = "DeviceCodeFormat"; + public const string RefreshToken = "RefreshTokenFormat"; + public const string UserCode = "UserCodeFormat"; + } - public static class Handlers - { - public const string Server = "OpenIdConnectServerHandler"; - } + public static class Handlers + { + public const string Server = "OpenIdConnectServerHandler"; + } - public static class Schemes - { - public const string Server = "ASOS"; - } + public static class Schemes + { + public const string Server = "ASOS"; } } } diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs index 5b3247a6..4e9e1dcd 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs @@ -12,67 +12,66 @@ using OpenIddict.Server; using OpenIddict.Server.DataProtection; using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHandlers; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict ASP.NET Core Data Protection server services. +/// +public static class OpenIddictServerDataProtectionExtensions { /// - /// Exposes extensions allowing to register the OpenIddict ASP.NET Core Data Protection server services. + /// Registers the OpenIddict ASP.NET Core Data Protection server services in the DI container + /// and configures OpenIddict to validate and issue ASP.NET Data Protection-based tokens. /// - public static class OpenIddictServerDataProtectionExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerDataProtectionBuilder UseDataProtection(this OpenIddictServerBuilder builder) { - /// - /// Registers the OpenIddict ASP.NET Core Data Protection server services in the DI container - /// and configures OpenIddict to validate and issue ASP.NET Data Protection-based tokens. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictServerDataProtectionBuilder UseDataProtection(this OpenIddictServerBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddDataProtection(); + builder.Services.AddDataProtection(); - // Register the built-in server event handlers used by the OpenIddict Data Protection components. - // Note: the order used here is not important, as the actual order is set in the options. - builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); + // Register the built-in server event handlers used by the OpenIddict Data Protection components. + // Note: the order used here is not important, as the actual order is set in the options. + builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); - // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. - builder.Services.TryAddEnumerable(new[] - { - ServiceDescriptor.Singleton, OpenIddictServerDataProtectionConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictServerDataProtectionConfiguration>() - }); + // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. + builder.Services.TryAddEnumerable(new[] + { + ServiceDescriptor.Singleton, OpenIddictServerDataProtectionConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictServerDataProtectionConfiguration>() + }); - return new OpenIddictServerDataProtectionBuilder(builder.Services); - } + return new OpenIddictServerDataProtectionBuilder(builder.Services); + } - /// - /// Registers the OpenIddict ASP.NET Core Data Protection server services in the DI container - /// and configures OpenIddict to validate and issue ASP.NET Data Protection-based tokens. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the server services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictServerBuilder UseDataProtection( - this OpenIddictServerBuilder builder, Action configuration) + /// + /// Registers the OpenIddict ASP.NET Core Data Protection server services in the DI container + /// and configures OpenIddict to validate and issue ASP.NET Data Protection-based tokens. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the server services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerBuilder UseDataProtection( + this OpenIddictServerBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseDataProtection()); + configuration(builder.UseDataProtection()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs index 2cc0fedf..7633b638 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs @@ -18,398 +18,397 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using Properties = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.DataProtection +namespace OpenIddict.Server.DataProtection; + +public class OpenIddictServerDataProtectionFormatter : IOpenIddictServerDataProtectionFormatter { - public class OpenIddictServerDataProtectionFormatter : IOpenIddictServerDataProtectionFormatter + public ClaimsPrincipal ReadToken(BinaryReader reader) { - public ClaimsPrincipal ReadToken(BinaryReader reader) + if (reader is null) { - if (reader is null) - { - throw new ArgumentNullException(nameof(reader)); - } + throw new ArgumentNullException(nameof(reader)); + } - var (principal, properties) = Read(reader); - - // Tokens serialized using the ASP.NET Core Data Protection stack are compound - // of both claims and special authentication properties. To ensure existing tokens - // can be reused, well-known properties are manually mapped to their claims equivalents. - - return principal - .SetAudiences(GetArrayProperty(properties, Properties.Audiences)) - .SetPresenters(GetArrayProperty(properties, Properties.Presenters)) - .SetResources(GetArrayProperty(properties, Properties.Resources)) - .SetScopes(GetArrayProperty(properties, Properties.Scopes)) - - .SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime)) - .SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime)) - .SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId)) - .SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge)) - .SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod)) - .SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued)) - .SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId)) - .SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime)) - .SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime)) - .SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires)) - .SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce)) - .SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri)) - .SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime)) - .SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId)) - .SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime)); - - static (ClaimsPrincipal principal, IReadOnlyDictionary properties) Read(BinaryReader reader) + var (principal, properties) = Read(reader); + + // Tokens serialized using the ASP.NET Core Data Protection stack are compound + // of both claims and special authentication properties. To ensure existing tokens + // can be reused, well-known properties are manually mapped to their claims equivalents. + + return principal + .SetAudiences(GetArrayProperty(properties, Properties.Audiences)) + .SetPresenters(GetArrayProperty(properties, Properties.Presenters)) + .SetResources(GetArrayProperty(properties, Properties.Resources)) + .SetScopes(GetArrayProperty(properties, Properties.Scopes)) + + .SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime)) + .SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime)) + .SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId)) + .SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge)) + .SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod)) + .SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued)) + .SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId)) + .SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime)) + .SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime)) + .SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires)) + .SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce)) + .SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri)) + .SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime)) + .SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId)) + .SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime)); + + static (ClaimsPrincipal principal, IReadOnlyDictionary properties) Read(BinaryReader reader) + { + // Read the version of the format used to serialize the ticket. + var version = reader.ReadInt32(); + if (version != 5) { - // Read the version of the format used to serialize the ticket. - var version = reader.ReadInt32(); - if (version != 5) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0287)); - } - - // Read the authentication scheme associated to the ticket. - _ = reader.ReadString(); - - // Read the number of identities stored in the serialized payload. - var count = reader.ReadInt32(); - - var identities = new ClaimsIdentity[count]; - for (var index = 0; index != count; ++index) - { - identities[index] = ReadIdentity(reader); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0287)); + } - var properties = ReadProperties(reader); + // Read the authentication scheme associated to the ticket. + _ = reader.ReadString(); - return (new ClaimsPrincipal(identities), properties); - } + // Read the number of identities stored in the serialized payload. + var count = reader.ReadInt32(); - static ClaimsIdentity ReadIdentity(BinaryReader reader) + var identities = new ClaimsIdentity[count]; + for (var index = 0; index != count; ++index) { - var identity = new ClaimsIdentity( - authenticationType: reader.ReadString(), - nameType: ReadWithDefault(reader, ClaimsIdentity.DefaultNameClaimType), - roleType: ReadWithDefault(reader, ClaimsIdentity.DefaultRoleClaimType)); + identities[index] = ReadIdentity(reader); + } - // Read the number of claims contained in the serialized identity. - var count = reader.ReadInt32(); + var properties = ReadProperties(reader); - for (int index = 0; index != count; ++index) - { - var claim = ReadClaim(reader, identity); + return (new ClaimsPrincipal(identities), properties); + } - identity.AddClaim(claim); - } + static ClaimsIdentity ReadIdentity(BinaryReader reader) + { + var identity = new ClaimsIdentity( + authenticationType: reader.ReadString(), + nameType: ReadWithDefault(reader, ClaimsIdentity.DefaultNameClaimType), + roleType: ReadWithDefault(reader, ClaimsIdentity.DefaultRoleClaimType)); - // Determine whether the identity has a bootstrap context attached. - if (reader.ReadBoolean()) - { - identity.BootstrapContext = reader.ReadString(); - } + // Read the number of claims contained in the serialized identity. + var count = reader.ReadInt32(); - // Determine whether the identity has an actor identity attached. - if (reader.ReadBoolean()) - { - identity.Actor = ReadIdentity(reader); - } + for (int index = 0; index != count; ++index) + { + var claim = ReadClaim(reader, identity); - return identity; + identity.AddClaim(claim); } - static Claim ReadClaim(BinaryReader reader, ClaimsIdentity identity) + // Determine whether the identity has a bootstrap context attached. + if (reader.ReadBoolean()) { - var type = ReadWithDefault(reader, identity.NameClaimType); - var value = reader.ReadString(); - var valueType = ReadWithDefault(reader, ClaimValueTypes.String); - var issuer = ReadWithDefault(reader, ClaimsIdentity.DefaultIssuer); - var originalIssuer = ReadWithDefault(reader, issuer); + identity.BootstrapContext = reader.ReadString(); + } - var claim = new Claim(type, value, valueType, issuer, originalIssuer, identity); + // Determine whether the identity has an actor identity attached. + if (reader.ReadBoolean()) + { + identity.Actor = ReadIdentity(reader); + } - // Read the number of properties stored in the claim. - var count = reader.ReadInt32(); + return identity; + } - for (var index = 0; index != count; ++index) - { - var key = reader.ReadString(); - var propertyValue = reader.ReadString(); + static Claim ReadClaim(BinaryReader reader, ClaimsIdentity identity) + { + var type = ReadWithDefault(reader, identity.NameClaimType); + var value = reader.ReadString(); + var valueType = ReadWithDefault(reader, ClaimValueTypes.String); + var issuer = ReadWithDefault(reader, ClaimsIdentity.DefaultIssuer); + var originalIssuer = ReadWithDefault(reader, issuer); - claim.Properties.Add(key, propertyValue); - } + var claim = new Claim(type, value, valueType, issuer, originalIssuer, identity); - return claim; - } + // Read the number of properties stored in the claim. + var count = reader.ReadInt32(); - static IReadOnlyDictionary ReadProperties(BinaryReader reader) + for (var index = 0; index != count; ++index) { - // Read the version of the format used to serialize the properties. - var version = reader.ReadInt32(); - if (version != 1) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0287)); - } + var key = reader.ReadString(); + var propertyValue = reader.ReadString(); - var count = reader.ReadInt32(); - var properties = new Dictionary(count, StringComparer.Ordinal); - for (var index = 0; index != count; ++index) - { - properties.Add(reader.ReadString(), reader.ReadString()); - } + claim.Properties.Add(key, propertyValue); + } + + return claim; + } - return properties; + static IReadOnlyDictionary ReadProperties(BinaryReader reader) + { + // Read the version of the format used to serialize the properties. + var version = reader.ReadInt32(); + if (version != 1) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0287)); } - static string ReadWithDefault(BinaryReader reader, string defaultValue) + var count = reader.ReadInt32(); + var properties = new Dictionary(count, StringComparer.Ordinal); + for (var index = 0; index != count; ++index) { - var value = reader.ReadString(); + properties.Add(reader.ReadString(), reader.ReadString()); + } - if (string.Equals(value, "\0", StringComparison.Ordinal)) - { - return defaultValue; - } + return properties; + } + + static string ReadWithDefault(BinaryReader reader, string defaultValue) + { + var value = reader.ReadString(); - return value; + if (string.Equals(value, "\0", StringComparison.Ordinal)) + { + return defaultValue; } - static string? GetProperty(IReadOnlyDictionary properties, string name) - => properties.TryGetValue(name, out var value) ? value : null; + return value; + } + + static string? GetProperty(IReadOnlyDictionary properties, string name) + => properties.TryGetValue(name, out var value) ? value : null; - static ImmutableArray GetArrayProperty(IReadOnlyDictionary properties, string name) + static ImmutableArray GetArrayProperty(IReadOnlyDictionary properties, string name) + { + if (properties.TryGetValue(name, out var value)) { - if (properties.TryGetValue(name, out var value)) - { - using var document = JsonDocument.Parse(value); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); + using var document = JsonDocument.Parse(value); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - foreach (var element in document.RootElement.EnumerateArray()) + foreach (var element in document.RootElement.EnumerateArray()) + { + var item = element.GetString(); + if (string.IsNullOrEmpty(item)) { - var item = element.GetString(); - if (string.IsNullOrEmpty(item)) - { - continue; - } - - builder.Add(item); + continue; } - return builder.ToImmutable(); + builder.Add(item); } - return ImmutableArray.Create(); + return builder.ToImmutable(); } + + return ImmutableArray.Create(); } + } - public void WriteToken(BinaryWriter writer, ClaimsPrincipal principal) + public void WriteToken(BinaryWriter writer, ClaimsPrincipal principal) + { + if (writer is null) { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } + throw new ArgumentNullException(nameof(writer)); + } + + if (principal is null) + { + throw new ArgumentNullException(nameof(principal)); + } + + var properties = new Dictionary(); + + // Unlike ASP.NET Core Data Protection-based tokens, tokens serialized using the new format + // can't include authentication properties. To ensure tokens can be used with previous versions + // of OpenIddict (1.x/2.x), well-known claims are manually mapped to their properties equivalents. + + SetProperty(properties, Properties.Issued, principal.GetClaim(Claims.Private.CreationDate)); + SetProperty(properties, Properties.Expires, principal.GetClaim(Claims.Private.ExpirationDate)); + + SetProperty(properties, Properties.AccessTokenLifetime, principal.GetClaim(Claims.Private.AccessTokenLifetime)); + SetProperty(properties, Properties.AuthorizationCodeLifetime, principal.GetClaim(Claims.Private.AuthorizationCodeLifetime)); + SetProperty(properties, Properties.DeviceCodeLifetime, principal.GetClaim(Claims.Private.DeviceCodeLifetime)); + SetProperty(properties, Properties.IdentityTokenLifetime, principal.GetClaim(Claims.Private.IdentityTokenLifetime)); + SetProperty(properties, Properties.RefreshTokenLifetime, principal.GetClaim(Claims.Private.RefreshTokenLifetime)); + SetProperty(properties, Properties.UserCodeLifetime, principal.GetClaim(Claims.Private.UserCodeLifetime)); + + SetProperty(properties, Properties.CodeChallenge, principal.GetClaim(Claims.Private.CodeChallenge)); + SetProperty(properties, Properties.CodeChallengeMethod, principal.GetClaim(Claims.Private.CodeChallengeMethod)); + + SetProperty(properties, Properties.InternalAuthorizationId, principal.GetAuthorizationId()); + SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId()); + + SetProperty(properties, Properties.DeviceCodeId, principal.GetClaim(Claims.Private.DeviceCodeId)); + SetProperty(properties, Properties.Nonce, principal.GetClaim(Claims.Private.Nonce)); + SetProperty(properties, Properties.OriginalRedirectUri, principal.GetClaim(Claims.Private.RedirectUri)); + + SetArrayProperty(properties, Properties.Audiences, principal.GetAudiences()); + SetArrayProperty(properties, Properties.Presenters, principal.GetPresenters()); + SetArrayProperty(properties, Properties.Resources, principal.GetResources()); + SetArrayProperty(properties, Properties.Scopes, principal.GetScopes()); + + // Copy the principal and exclude the claim that were mapped to authentication properties. + principal = principal.Clone(claim => claim.Type is not ( + Claims.Private.AccessTokenLifetime or + Claims.Private.Audience or + Claims.Private.AuthorizationCodeLifetime or + Claims.Private.AuthorizationId or + Claims.Private.CodeChallenge or + Claims.Private.CodeChallengeMethod or + Claims.Private.CreationDate or + Claims.Private.DeviceCodeId or + Claims.Private.DeviceCodeLifetime or + Claims.Private.ExpirationDate or + Claims.Private.IdentityTokenLifetime or + Claims.Private.Nonce or + Claims.Private.Presenter or + Claims.Private.RedirectUri or + Claims.Private.RefreshTokenLifetime or + Claims.Private.Resource or + Claims.Private.Scope or + Claims.Private.TokenId or + Claims.Private.UserCodeLifetime)); + + Write(writer, principal.Identity?.AuthenticationType, principal, properties); + writer.Flush(); + + // Note: the following local methods closely matches the logic used by ASP.NET Core's + // authentication stack and MUST NOT be modified to ensure tokens encrypted using + // the OpenID Connect server middleware can be read by OpenIddict (and vice-versa). + + static void Write(BinaryWriter writer, string? scheme, ClaimsPrincipal principal, IReadOnlyDictionary properties) + { + // Write the version of the format used to serialize the ticket. + writer.Write(/* version: */ 5); + writer.Write(scheme ?? string.Empty); - if (principal is null) + // Write the number of identities contained in the principal. + writer.Write(principal.Identities.Count()); + + foreach (var identity in principal.Identities) { - throw new ArgumentNullException(nameof(principal)); + WriteIdentity(writer, identity); } - var properties = new Dictionary(); - - // Unlike ASP.NET Core Data Protection-based tokens, tokens serialized using the new format - // can't include authentication properties. To ensure tokens can be used with previous versions - // of OpenIddict (1.x/2.x), well-known claims are manually mapped to their properties equivalents. - - SetProperty(properties, Properties.Issued, principal.GetClaim(Claims.Private.CreationDate)); - SetProperty(properties, Properties.Expires, principal.GetClaim(Claims.Private.ExpirationDate)); - - SetProperty(properties, Properties.AccessTokenLifetime, principal.GetClaim(Claims.Private.AccessTokenLifetime)); - SetProperty(properties, Properties.AuthorizationCodeLifetime, principal.GetClaim(Claims.Private.AuthorizationCodeLifetime)); - SetProperty(properties, Properties.DeviceCodeLifetime, principal.GetClaim(Claims.Private.DeviceCodeLifetime)); - SetProperty(properties, Properties.IdentityTokenLifetime, principal.GetClaim(Claims.Private.IdentityTokenLifetime)); - SetProperty(properties, Properties.RefreshTokenLifetime, principal.GetClaim(Claims.Private.RefreshTokenLifetime)); - SetProperty(properties, Properties.UserCodeLifetime, principal.GetClaim(Claims.Private.UserCodeLifetime)); - - SetProperty(properties, Properties.CodeChallenge, principal.GetClaim(Claims.Private.CodeChallenge)); - SetProperty(properties, Properties.CodeChallengeMethod, principal.GetClaim(Claims.Private.CodeChallengeMethod)); - - SetProperty(properties, Properties.InternalAuthorizationId, principal.GetAuthorizationId()); - SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId()); - - SetProperty(properties, Properties.DeviceCodeId, principal.GetClaim(Claims.Private.DeviceCodeId)); - SetProperty(properties, Properties.Nonce, principal.GetClaim(Claims.Private.Nonce)); - SetProperty(properties, Properties.OriginalRedirectUri, principal.GetClaim(Claims.Private.RedirectUri)); - - SetArrayProperty(properties, Properties.Audiences, principal.GetAudiences()); - SetArrayProperty(properties, Properties.Presenters, principal.GetPresenters()); - SetArrayProperty(properties, Properties.Resources, principal.GetResources()); - SetArrayProperty(properties, Properties.Scopes, principal.GetScopes()); - - // Copy the principal and exclude the claim that were mapped to authentication properties. - principal = principal.Clone(claim => claim.Type is not ( - Claims.Private.AccessTokenLifetime or - Claims.Private.Audience or - Claims.Private.AuthorizationCodeLifetime or - Claims.Private.AuthorizationId or - Claims.Private.CodeChallenge or - Claims.Private.CodeChallengeMethod or - Claims.Private.CreationDate or - Claims.Private.DeviceCodeId or - Claims.Private.DeviceCodeLifetime or - Claims.Private.ExpirationDate or - Claims.Private.IdentityTokenLifetime or - Claims.Private.Nonce or - Claims.Private.Presenter or - Claims.Private.RedirectUri or - Claims.Private.RefreshTokenLifetime or - Claims.Private.Resource or - Claims.Private.Scope or - Claims.Private.TokenId or - Claims.Private.UserCodeLifetime)); - - Write(writer, principal.Identity?.AuthenticationType, principal, properties); - writer.Flush(); - - // Note: the following local methods closely matches the logic used by ASP.NET Core's - // authentication stack and MUST NOT be modified to ensure tokens encrypted using - // the OpenID Connect server middleware can be read by OpenIddict (and vice-versa). - - static void Write(BinaryWriter writer, string? scheme, ClaimsPrincipal principal, IReadOnlyDictionary properties) - { - // Write the version of the format used to serialize the ticket. - writer.Write(/* version: */ 5); - writer.Write(scheme ?? string.Empty); + WriteProperties(writer, properties); + } - // Write the number of identities contained in the principal. - writer.Write(principal.Identities.Count()); + static void WriteIdentity(BinaryWriter writer, ClaimsIdentity identity) + { + writer.Write(identity.AuthenticationType ?? string.Empty); + WriteWithDefault(writer, identity.NameClaimType, ClaimsIdentity.DefaultNameClaimType); + WriteWithDefault(writer, identity.RoleClaimType, ClaimsIdentity.DefaultRoleClaimType); - foreach (var identity in principal.Identities) - { - WriteIdentity(writer, identity); - } + // Write the number of claims contained in the identity. + writer.Write(identity.Claims.Count()); - WriteProperties(writer, properties); + foreach (var claim in identity.Claims) + { + WriteClaim(writer, claim); } - static void WriteIdentity(BinaryWriter writer, ClaimsIdentity identity) + var bootstrap = identity.BootstrapContext as string; + if (!string.IsNullOrEmpty(bootstrap)) { - writer.Write(identity.AuthenticationType ?? string.Empty); - WriteWithDefault(writer, identity.NameClaimType, ClaimsIdentity.DefaultNameClaimType); - WriteWithDefault(writer, identity.RoleClaimType, ClaimsIdentity.DefaultRoleClaimType); - - // Write the number of claims contained in the identity. - writer.Write(identity.Claims.Count()); - - foreach (var claim in identity.Claims) - { - WriteClaim(writer, claim); - } - - var bootstrap = identity.BootstrapContext as string; - if (!string.IsNullOrEmpty(bootstrap)) - { - writer.Write(true); - writer.Write(bootstrap); - } + writer.Write(true); + writer.Write(bootstrap); + } - else - { - writer.Write(false); - } + else + { + writer.Write(false); + } - if (identity.Actor is not null) - { - writer.Write(true); - WriteIdentity(writer, identity.Actor); - } + if (identity.Actor is not null) + { + writer.Write(true); + WriteIdentity(writer, identity.Actor); + } - else - { - writer.Write(false); - } + else + { + writer.Write(false); } + } - static void WriteClaim(BinaryWriter writer, Claim claim) + static void WriteClaim(BinaryWriter writer, Claim claim) + { + if (writer is null) { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } + throw new ArgumentNullException(nameof(writer)); + } - if (claim is null) - { - throw new ArgumentNullException(nameof(claim)); - } + if (claim is null) + { + throw new ArgumentNullException(nameof(claim)); + } - WriteWithDefault(writer, claim.Type, claim.Subject?.NameClaimType ?? ClaimsIdentity.DefaultNameClaimType); - writer.Write(claim.Value); - WriteWithDefault(writer, claim.ValueType, ClaimValueTypes.String); - WriteWithDefault(writer, claim.Issuer, ClaimsIdentity.DefaultIssuer); - WriteWithDefault(writer, claim.OriginalIssuer, claim.Issuer); + WriteWithDefault(writer, claim.Type, claim.Subject?.NameClaimType ?? ClaimsIdentity.DefaultNameClaimType); + writer.Write(claim.Value); + WriteWithDefault(writer, claim.ValueType, ClaimValueTypes.String); + WriteWithDefault(writer, claim.Issuer, ClaimsIdentity.DefaultIssuer); + WriteWithDefault(writer, claim.OriginalIssuer, claim.Issuer); - // Write the number of properties contained in the claim. - writer.Write(claim.Properties.Count); + // Write the number of properties contained in the claim. + writer.Write(claim.Properties.Count); - foreach (var property in claim.Properties) - { - writer.Write(property.Key ?? string.Empty); - writer.Write(property.Value ?? string.Empty); - } + foreach (var property in claim.Properties) + { + writer.Write(property.Key ?? string.Empty); + writer.Write(property.Value ?? string.Empty); } + } - static void WriteProperties(BinaryWriter writer, IReadOnlyDictionary properties) - { - // Write the version of the format used to serialize the properties. - writer.Write(/* version: */ 1); - writer.Write(properties.Count); + static void WriteProperties(BinaryWriter writer, IReadOnlyDictionary properties) + { + // Write the version of the format used to serialize the properties. + writer.Write(/* version: */ 1); + writer.Write(properties.Count); - foreach (var property in properties) - { - writer.Write(property.Key ?? string.Empty); - writer.Write(property.Value ?? string.Empty); - } + foreach (var property in properties) + { + writer.Write(property.Key ?? string.Empty); + writer.Write(property.Value ?? string.Empty); } + } - static void WriteWithDefault(BinaryWriter writer, string value, string defaultValue) - => writer.Write(string.Equals(value, defaultValue, StringComparison.Ordinal) ? "\0" : value); + static void WriteWithDefault(BinaryWriter writer, string value, string defaultValue) + => writer.Write(string.Equals(value, defaultValue, StringComparison.Ordinal) ? "\0" : value); - static void SetProperty(IDictionary properties, string name, string? value) + static void SetProperty(IDictionary properties, string name, string? value) + { + if (string.IsNullOrEmpty(value)) { - if (string.IsNullOrEmpty(value)) - { - properties.Remove(name); - } + properties.Remove(name); + } - else - { - properties[name] = value; - } + else + { + properties[name] = value; } + } - static void SetArrayProperty(IDictionary properties, string name, ImmutableArray values) + static void SetArrayProperty(IDictionary properties, string name, ImmutableArray values) + { + if (values.IsDefaultOrEmpty) { - if (values.IsDefaultOrEmpty) - { - properties.Remove(name); - } + properties.Remove(name); + } - else + else + { + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - writer.WriteStartArray(); + writer.WriteStartArray(); - foreach (var value in values) - { - writer.WriteStringValue(value); - } + foreach (var value in values) + { + writer.WriteStringValue(value); + } - writer.WriteEndArray(); - writer.Flush(); + writer.WriteEndArray(); + writer.Flush(); - properties[name] = Encoding.UTF8.GetString(stream.ToArray()); - } + properties[name] = Encoding.UTF8.GetString(stream.ToArray()); } } } diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs index 096bce25..0607dedd 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs @@ -22,310 +22,309 @@ using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using Schemes = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes.Schemes; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.DataProtection +namespace OpenIddict.Server.DataProtection; + +public static partial class OpenIddictServerDataProtectionHandlers { - public static partial class OpenIddictServerDataProtectionHandlers + public static class Protection { - public static class Protection + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Token validation: + */ + ValidateDataProtectionToken.Descriptor, + + /* + * Token generation: + */ + GenerateDataProtectionToken.Descriptor); + + /// + /// Contains the logic responsible of validating tokens generated using Data Protection. + /// + public class ValidateDataProtectionToken : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Token validation: - */ - ValidateDataProtectionToken.Descriptor, + private readonly IOptionsMonitor _options; - /* - * Token generation: - */ - GenerateDataProtectionToken.Descriptor); + public ValidateDataProtectionToken(IOptionsMonitor options) + => _options = options; /// - /// Contains the logic responsible of validating tokens generated using Data Protection. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateDataProtectionToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - private readonly IOptionsMonitor _options; - - public ValidateDataProtectionToken(IOptionsMonitor options) - => _options = options; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) + // If a principal was already attached, don't overwrite it. + if (context.Principal is not null) { - // If a principal was already attached, don't overwrite it. - if (context.Principal is not null) - { - return default; - } + return default; + } - // Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds - // to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads. - if (!context.Token.StartsWith("CfDJ8", StringComparison.Ordinal)) - { - return default; - } + // Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds + // to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads. + if (!context.Token.StartsWith("CfDJ8", StringComparison.Ordinal)) + { + return default; + } - // Tokens generated using ASP.NET Core Data Protection are encrypted by symmetric keys - // that are derived from both a master key resolved from the key ring and a specific value - // known as "purpose" that helps ensure that Data Protection payloads can't be decrypted - // without the correct "purpose" value, which is different for all types of tokens. + // Tokens generated using ASP.NET Core Data Protection are encrypted by symmetric keys + // that are derived from both a master key resolved from the key ring and a specific value + // known as "purpose" that helps ensure that Data Protection payloads can't be decrypted + // without the correct "purpose" value, which is different for all types of tokens. + // + // While offering extensive protection at the cryptographic level, this prevents decrypting + // unknown tokens without re-executing the entire decryption routine for each type of token + // considered valid. To speed up this process when supporting multiple types is required, + // the Data Protection integration relies on the "token_type_hint" parameter specified + // by the client when it is available (e.g with introspection or revocation requests). + + var principal = context.ValidTokenTypes.Count switch + { + // If no valid token type was set, all supported token types are allowed. // - // While offering extensive protection at the cryptographic level, this prevents decrypting - // unknown tokens without re-executing the entire decryption routine for each type of token - // considered valid. To speed up this process when supporting multiple types is required, - // the Data Protection integration relies on the "token_type_hint" parameter specified - // by the client when it is available (e.g with introspection or revocation requests). - - var principal = context.ValidTokenTypes.Count switch + // Note: if a "token_type_hint" was specified by the client, use it to optimize + // the token decryption lookup but fall back to other types of tokens + // if the token can't be decrypted using the specified token type hint. + // + // In this case, common types (e.g access/refresh tokens) are checked first. + 0 => context.TokenTypeHint switch + { + TokenTypeHints.AuthorizationCode => + ValidateToken(TokenTypeHints.AuthorizationCode) ?? + ValidateToken(TokenTypeHints.AccessToken) ?? + ValidateToken(TokenTypeHints.RefreshToken) ?? + ValidateToken(TokenTypeHints.DeviceCode) ?? + ValidateToken(TokenTypeHints.UserCode), + + TokenTypeHints.DeviceCode => + ValidateToken(TokenTypeHints.DeviceCode) ?? + ValidateToken(TokenTypeHints.AccessToken) ?? + ValidateToken(TokenTypeHints.RefreshToken) ?? + ValidateToken(TokenTypeHints.AuthorizationCode) ?? + ValidateToken(TokenTypeHints.UserCode), + + TokenTypeHints.RefreshToken => + ValidateToken(TokenTypeHints.RefreshToken) ?? + ValidateToken(TokenTypeHints.AccessToken) ?? + ValidateToken(TokenTypeHints.AuthorizationCode) ?? + ValidateToken(TokenTypeHints.DeviceCode) ?? + ValidateToken(TokenTypeHints.UserCode), + + TokenTypeHints.UserCode => + ValidateToken(TokenTypeHints.UserCode) ?? + ValidateToken(TokenTypeHints.AccessToken) ?? + ValidateToken(TokenTypeHints.RefreshToken) ?? + ValidateToken(TokenTypeHints.AuthorizationCode) ?? + ValidateToken(TokenTypeHints.DeviceCode), + + _ => + ValidateToken(TokenTypeHints.AccessToken) ?? + ValidateToken(TokenTypeHints.RefreshToken) ?? + ValidateToken(TokenTypeHints.AuthorizationCode) ?? + ValidateToken(TokenTypeHints.DeviceCode) ?? + ValidateToken(TokenTypeHints.UserCode), + }, + + // If a single valid token type was set, ignore the specified token type hint. + 1 => context.ValidTokenTypes.ElementAt(0) switch { - // If no valid token type was set, all supported token types are allowed. - // - // Note: if a "token_type_hint" was specified by the client, use it to optimize - // the token decryption lookup but fall back to other types of tokens - // if the token can't be decrypted using the specified token type hint. - // - // In this case, common types (e.g access/refresh tokens) are checked first. - 0 => context.TokenTypeHint switch - { - TokenTypeHints.AuthorizationCode => - ValidateToken(TokenTypeHints.AuthorizationCode) ?? - ValidateToken(TokenTypeHints.AccessToken) ?? - ValidateToken(TokenTypeHints.RefreshToken) ?? - ValidateToken(TokenTypeHints.DeviceCode) ?? - ValidateToken(TokenTypeHints.UserCode), - - TokenTypeHints.DeviceCode => - ValidateToken(TokenTypeHints.DeviceCode) ?? - ValidateToken(TokenTypeHints.AccessToken) ?? - ValidateToken(TokenTypeHints.RefreshToken) ?? - ValidateToken(TokenTypeHints.AuthorizationCode) ?? - ValidateToken(TokenTypeHints.UserCode), - - TokenTypeHints.RefreshToken => - ValidateToken(TokenTypeHints.RefreshToken) ?? - ValidateToken(TokenTypeHints.AccessToken) ?? - ValidateToken(TokenTypeHints.AuthorizationCode) ?? - ValidateToken(TokenTypeHints.DeviceCode) ?? - ValidateToken(TokenTypeHints.UserCode), - - TokenTypeHints.UserCode => - ValidateToken(TokenTypeHints.UserCode) ?? - ValidateToken(TokenTypeHints.AccessToken) ?? - ValidateToken(TokenTypeHints.RefreshToken) ?? - ValidateToken(TokenTypeHints.AuthorizationCode) ?? - ValidateToken(TokenTypeHints.DeviceCode), - - _ => - ValidateToken(TokenTypeHints.AccessToken) ?? - ValidateToken(TokenTypeHints.RefreshToken) ?? - ValidateToken(TokenTypeHints.AuthorizationCode) ?? - ValidateToken(TokenTypeHints.DeviceCode) ?? - ValidateToken(TokenTypeHints.UserCode), - }, - - // If a single valid token type was set, ignore the specified token type hint. - 1 => context.ValidTokenTypes.ElementAt(0) switch - { - TokenTypeHints.AccessToken => ValidateToken(TokenTypeHints.AccessToken), - TokenTypeHints.RefreshToken => ValidateToken(TokenTypeHints.RefreshToken), - TokenTypeHints.AuthorizationCode => ValidateToken(TokenTypeHints.AuthorizationCode), - TokenTypeHints.DeviceCode => ValidateToken(TokenTypeHints.DeviceCode), - TokenTypeHints.UserCode => ValidateToken(TokenTypeHints.UserCode), - - _ => null // The token type is not supported by the Data Protection integration (e.g identity tokens). - }, - - // If multiple valid types were set, use the specified token type hint - // and select the first non-null token that can be successfully decrypted. - _ => context.ValidTokenTypes.OrderBy(type => type switch - { - // If the token type hint corresponds to one of the valid types, test it first. - string value when value == context.TokenTypeHint => 0, - - TokenTypeHints.AccessToken => 1, - TokenTypeHints.RefreshToken => 2, - TokenTypeHints.AuthorizationCode => 3, - TokenTypeHints.DeviceCode => 4, - TokenTypeHints.UserCode => 5, - - _ => int.MaxValue - }) - .Select(type => type switch - { - TokenTypeHints.AccessToken => ValidateToken(TokenTypeHints.AccessToken), - TokenTypeHints.RefreshToken => ValidateToken(TokenTypeHints.RefreshToken), - TokenTypeHints.AuthorizationCode => ValidateToken(TokenTypeHints.AuthorizationCode), - TokenTypeHints.DeviceCode => ValidateToken(TokenTypeHints.DeviceCode), - TokenTypeHints.UserCode => ValidateToken(TokenTypeHints.UserCode), - - _ => null // The token type is not supported by the Data Protection integration (e.g identity tokens). - }) - .Where(static principal => principal is not null) - .FirstOrDefault() - }; - - if (principal is null) + TokenTypeHints.AccessToken => ValidateToken(TokenTypeHints.AccessToken), + TokenTypeHints.RefreshToken => ValidateToken(TokenTypeHints.RefreshToken), + TokenTypeHints.AuthorizationCode => ValidateToken(TokenTypeHints.AuthorizationCode), + TokenTypeHints.DeviceCode => ValidateToken(TokenTypeHints.DeviceCode), + TokenTypeHints.UserCode => ValidateToken(TokenTypeHints.UserCode), + + _ => null // The token type is not supported by the Data Protection integration (e.g identity tokens). + }, + + // If multiple valid types were set, use the specified token type hint + // and select the first non-null token that can be successfully decrypted. + _ => context.ValidTokenTypes.OrderBy(type => type switch { - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2004), - uri: SR.FormatID8000(SR.ID2004)); + // If the token type hint corresponds to one of the valid types, test it first. + string value when value == context.TokenTypeHint => 0, - return default; - } + TokenTypeHints.AccessToken => 1, + TokenTypeHints.RefreshToken => 2, + TokenTypeHints.AuthorizationCode => 3, + TokenTypeHints.DeviceCode => 4, + TokenTypeHints.UserCode => 5, + + _ => int.MaxValue + }) + .Select(type => type switch + { + TokenTypeHints.AccessToken => ValidateToken(TokenTypeHints.AccessToken), + TokenTypeHints.RefreshToken => ValidateToken(TokenTypeHints.RefreshToken), + TokenTypeHints.AuthorizationCode => ValidateToken(TokenTypeHints.AuthorizationCode), + TokenTypeHints.DeviceCode => ValidateToken(TokenTypeHints.DeviceCode), + TokenTypeHints.UserCode => ValidateToken(TokenTypeHints.UserCode), - context.Principal = principal; + _ => null // The token type is not supported by the Data Protection integration (e.g identity tokens). + }) + .Where(static principal => principal is not null) + .FirstOrDefault() + }; - context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims); + if (principal is null) + { + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2004), + uri: SR.FormatID8000(SR.ID2004)); return default; - - ClaimsPrincipal? ValidateToken(string type) - { - // Create a Data Protection protector using the provider registered in the options. - var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch - { - // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. - TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId) - => new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, - TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, - - TokenTypeHints.AuthorizationCode when !string.IsNullOrEmpty(context.TokenId) - => new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server }, - TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server }, - - TokenTypeHints.DeviceCode when !string.IsNullOrEmpty(context.TokenId) - => new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server }, - TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server }, - - TokenTypeHints.RefreshToken when !string.IsNullOrEmpty(context.TokenId) - => new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server }, - TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server }, - - TokenTypeHints.UserCode when !string.IsNullOrEmpty(context.TokenId) - => new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server }, - TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server }, - - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) - }); - - try - { - using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(context.Token))); - using var reader = new BinaryReader(buffer); - - // Note: since the data format relies on a data protector using different "purposes" strings - // per token type, the token processed at this stage is guaranteed to be of the expected type. - return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type); - } - - catch (Exception exception) - { - context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), context.Token); - - return null; - } - } } - } - - /// - /// Contains the logic responsible of generating a token using Data Protection. - /// - public class GenerateDataProtectionToken : IOpenIddictServerHandler - { - private readonly IOptionsMonitor _options; - - public GenerateDataProtectionToken(IOptionsMonitor options) - => _options = options; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(GenerateIdentityModelToken.Descriptor.Order - 500) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(GenerateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - // If an access token was already attached by another handler, don't overwrite it. - if (!string.IsNullOrEmpty(context.Token)) - { - return default; - } + context.Principal = principal; - if (context.TokenType switch - { - TokenTypeHints.AccessToken => _options.CurrentValue.PreferDefaultAccessTokenFormat, - TokenTypeHints.AuthorizationCode => _options.CurrentValue.PreferDefaultAuthorizationCodeFormat, - TokenTypeHints.DeviceCode => _options.CurrentValue.PreferDefaultDeviceCodeFormat, - TokenTypeHints.RefreshToken => _options.CurrentValue.PreferDefaultRefreshTokenFormat, - TokenTypeHints.UserCode => _options.CurrentValue.PreferDefaultUserCodeFormat, + context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims); - _ => true // The token type is not supported by the Data Protection integration (e.g identity tokens). - }) - { - return default; - } + return default; + ClaimsPrincipal? ValidateToken(string type) + { // Create a Data Protection protector using the provider registered in the options. - var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(context.TokenType switch + var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch { // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. - TokenTypeHints.AccessToken when context.Options.UseReferenceAccessTokens + TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId) => new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, - TokenTypeHints.AuthorizationCode when !context.Options.DisableTokenStorage + TokenTypeHints.AuthorizationCode when !string.IsNullOrEmpty(context.TokenId) => new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server }, TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server }, - TokenTypeHints.DeviceCode when !context.Options.DisableTokenStorage + TokenTypeHints.DeviceCode when !string.IsNullOrEmpty(context.TokenId) => new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server }, TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server }, - TokenTypeHints.RefreshToken when context.Options.UseReferenceRefreshTokens + TokenTypeHints.RefreshToken when !string.IsNullOrEmpty(context.TokenId) => new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server }, TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server }, - TokenTypeHints.UserCode when !context.Options.DisableTokenStorage + TokenTypeHints.UserCode when !string.IsNullOrEmpty(context.TokenId) => new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server }, TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server }, _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) }); - using var buffer = new MemoryStream(); - using var writer = new BinaryWriter(buffer); + try + { + using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(context.Token))); + using var reader = new BinaryReader(buffer); + + // Note: since the data format relies on a data protector using different "purposes" strings + // per token type, the token processed at this stage is guaranteed to be of the expected type. + return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type); + } + + catch (Exception exception) + { + context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), context.Token); + + return null; + } + } + } + } + + /// + /// Contains the logic responsible of generating a token using Data Protection. + /// + public class GenerateDataProtectionToken : IOpenIddictServerHandler + { + private readonly IOptionsMonitor _options; - _options.CurrentValue.Formatter.WriteToken(writer, context.Principal); + public GenerateDataProtectionToken(IOptionsMonitor options) + => _options = options; - context.Token = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray())); + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(GenerateIdentityModelToken.Descriptor.Order - 500) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(GenerateTokenContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType, - context.Token, context.Principal.Claims); + // If an access token was already attached by another handler, don't overwrite it. + if (!string.IsNullOrEmpty(context.Token)) + { + return default; + } + if (context.TokenType switch + { + TokenTypeHints.AccessToken => _options.CurrentValue.PreferDefaultAccessTokenFormat, + TokenTypeHints.AuthorizationCode => _options.CurrentValue.PreferDefaultAuthorizationCodeFormat, + TokenTypeHints.DeviceCode => _options.CurrentValue.PreferDefaultDeviceCodeFormat, + TokenTypeHints.RefreshToken => _options.CurrentValue.PreferDefaultRefreshTokenFormat, + TokenTypeHints.UserCode => _options.CurrentValue.PreferDefaultUserCodeFormat, + + _ => true // The token type is not supported by the Data Protection integration (e.g identity tokens). + }) + { return default; } + + // Create a Data Protection protector using the provider registered in the options. + var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(context.TokenType switch + { + // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. + TokenTypeHints.AccessToken when context.Options.UseReferenceAccessTokens + => new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, + TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, + + TokenTypeHints.AuthorizationCode when !context.Options.DisableTokenStorage + => new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server }, + TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server }, + + TokenTypeHints.DeviceCode when !context.Options.DisableTokenStorage + => new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server }, + TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server }, + + TokenTypeHints.RefreshToken when context.Options.UseReferenceRefreshTokens + => new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server }, + TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server }, + + TokenTypeHints.UserCode when !context.Options.DisableTokenStorage + => new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server }, + TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server }, + + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) + }); + + using var buffer = new MemoryStream(); + using var writer = new BinaryWriter(buffer); + + _options.CurrentValue.Formatter.WriteToken(writer, context.Principal); + + context.Token = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray())); + + context.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType, + context.Token, context.Principal.Claims); + + return default; } } } diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs index 2463b253..3e539c1a 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs @@ -7,12 +7,11 @@ using System.Collections.Immutable; using System.ComponentModel; -namespace OpenIddict.Server.DataProtection +namespace OpenIddict.Server.DataProtection; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class OpenIddictServerDataProtectionHandlers { - [EditorBrowsable(EditorBrowsableState.Never)] - public static partial class OpenIddictServerDataProtectionHandlers - { - public static ImmutableArray DefaultHandlers { get; } - = ImmutableArray.CreateRange(Protection.DefaultHandlers); - } + public static ImmutableArray DefaultHandlers { get; } + = ImmutableArray.CreateRange(Protection.DefaultHandlers); } diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs index ee06b40e..a943c626 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs @@ -6,56 +6,55 @@ using Microsoft.AspNetCore.DataProtection; -namespace OpenIddict.Server.DataProtection +namespace OpenIddict.Server.DataProtection; + +/// +/// Provides various settings needed to configure the OpenIddict +/// ASP.NET Core Data Protection server integration. +/// +public class OpenIddictServerDataProtectionOptions { /// - /// Provides various settings needed to configure the OpenIddict - /// ASP.NET Core Data Protection server integration. + /// Gets or sets the data protection provider used to create the default + /// data protectors used by the OpenIddict Data Protection server services. + /// When this property is set to null, the data protection provider + /// is directly retrieved from the dependency injection container. + /// + public IDataProtectionProvider DataProtectionProvider { get; set; } = default!; + + /// + /// Gets or sets the formatter used to read and write Data Protection tokens. + /// + public IOpenIddictServerDataProtectionFormatter Formatter { get; set; } + = new OpenIddictServerDataProtectionFormatter(); + + /// + /// Gets or sets a boolean indicating whether the default access token format should be + /// used when issuing new access tokens. This property is set to false by default. + /// + public bool PreferDefaultAccessTokenFormat { get; set; } + + /// + /// Gets or sets a boolean indicating whether the default authorization code format should be + /// used when issuing new authorization codes. This property is set to false by default. + /// + public bool PreferDefaultAuthorizationCodeFormat { get; set; } + + /// + /// Gets or sets a boolean indicating whether the default device code format should be + /// used when issuing new device codes. This property is set to false by default. + /// + public bool PreferDefaultDeviceCodeFormat { get; set; } + + /// + /// Gets or sets a boolean indicating whether the default refresh token format should be + /// used when issuing new refresh tokens. This property is set to false by default. + /// + public bool PreferDefaultRefreshTokenFormat { get; set; } + + /// + /// Gets or sets a boolean indicating whether the default user code format should be + /// used when issuing new user codes. This property is set to false by default. /// - public class OpenIddictServerDataProtectionOptions - { - /// - /// Gets or sets the data protection provider used to create the default - /// data protectors used by the OpenIddict Data Protection server services. - /// When this property is set to null, the data protection provider - /// is directly retrieved from the dependency injection container. - /// - public IDataProtectionProvider DataProtectionProvider { get; set; } = default!; - - /// - /// Gets or sets the formatter used to read and write Data Protection tokens. - /// - public IOpenIddictServerDataProtectionFormatter Formatter { get; set; } - = new OpenIddictServerDataProtectionFormatter(); - - /// - /// Gets or sets a boolean indicating whether the default access token format should be - /// used when issuing new access tokens. This property is set to false by default. - /// - public bool PreferDefaultAccessTokenFormat { get; set; } - - /// - /// Gets or sets a boolean indicating whether the default authorization code format should be - /// used when issuing new authorization codes. This property is set to false by default. - /// - public bool PreferDefaultAuthorizationCodeFormat { get; set; } - - /// - /// Gets or sets a boolean indicating whether the default device code format should be - /// used when issuing new device codes. This property is set to false by default. - /// - public bool PreferDefaultDeviceCodeFormat { get; set; } - - /// - /// Gets or sets a boolean indicating whether the default refresh token format should be - /// used when issuing new refresh tokens. This property is set to false by default. - /// - public bool PreferDefaultRefreshTokenFormat { get; set; } - - /// - /// Gets or sets a boolean indicating whether the default user code format should be - /// used when issuing new user codes. This property is set to false by default. - /// - public bool PreferDefaultUserCodeFormat { get; set; } - } + public bool PreferDefaultUserCodeFormat { get; set; } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinBuilder.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinBuilder.cs index e7bfade9..07541c37 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinBuilder.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinBuilder.cs @@ -12,189 +12,188 @@ using OpenIddict.Server.Owin; using Owin; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure +/// the OpenIddict server OWIN/Katana integration. +/// +public class OpenIddictServerOwinBuilder { /// - /// Exposes the necessary methods required to configure - /// the OpenIddict server OWIN/Katana integration. + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictServerOwinBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict server OWIN/Katana configuration. /// - public class OpenIddictServerOwinBuilder + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictServerOwinBuilder Configure(Action configuration) { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictServerOwinBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Amends the default OpenIddict server OWIN/Katana configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictServerOwinBuilder Configure(Action configuration) + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Disables the transport security requirement (HTTPS). - /// - /// The . - public OpenIddictServerOwinBuilder DisableTransportSecurityRequirement() - => Configure(options => options.DisableTransportSecurityRequirement = true); - - /// - /// Enables the pass-through mode for the OpenID Connect authorization endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerOwinBuilder EnableAuthorizationEndpointPassthrough() - => Configure(options => options.EnableAuthorizationEndpointPassthrough = true); - - /// - /// Enables error pass-through support, so that the rest of the request processing pipeline is - /// automatically invoked when returning an error from the interactive authorization and logout endpoints. - /// When this option is enabled, special logic must be added to these actions to handle errors, that can be - /// retrieved using - /// - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerOwinBuilder EnableErrorPassthrough() - => Configure(options => options.EnableErrorPassthrough = true); - - /// - /// Enables the pass-through mode for the OpenID Connect logout endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerOwinBuilder EnableLogoutEndpointPassthrough() - => Configure(options => options.EnableLogoutEndpointPassthrough = true); - - /// - /// Enables the pass-through mode for the OpenID Connect token endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerOwinBuilder EnableTokenEndpointPassthrough() - => Configure(options => options.EnableTokenEndpointPassthrough = true); - - /// - /// Enables the pass-through mode for the OpenID Connect userinfo endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerOwinBuilder EnableUserinfoEndpointPassthrough() - => Configure(options => options.EnableUserinfoEndpointPassthrough = true); - - /// - /// Enables the pass-through mode for the OpenID Connect user verification endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - /// The . - public OpenIddictServerOwinBuilder EnableVerificationEndpointPassthrough() - => Configure(options => options.EnableVerificationEndpointPassthrough = true); - - /// - /// Enables authorization request caching, so that authorization requests - /// are automatically stored in the distributed cache, which allows flowing - /// large payloads across requests. Enabling this option is recommended - /// when using external authentication providers or when large GET or POST - /// OpenID Connect authorization requests support is required. - /// - /// The . - public OpenIddictServerOwinBuilder EnableAuthorizationRequestCaching() - => Configure(options => options.EnableAuthorizationRequestCaching = true); - - /// - /// Enables logout request caching, so that logout requests - /// are automatically stored in the distributed cache. - /// - /// The . - public OpenIddictServerOwinBuilder EnableLogoutRequestCaching() - => Configure(options => options.EnableLogoutRequestCaching = true); - - /// - /// Sets the realm returned to the caller as part of the WWW-Authenticate header. - /// - /// The issuer address. - /// The . - public OpenIddictServerOwinBuilder SetRealm(string realm) - { - if (string.IsNullOrEmpty(realm)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0107), nameof(realm)); - } + /// + /// Disables the transport security requirement (HTTPS). + /// + /// The . + public OpenIddictServerOwinBuilder DisableTransportSecurityRequirement() + => Configure(options => options.DisableTransportSecurityRequirement = true); - return Configure(options => options.Realm = realm); - } + /// + /// Enables the pass-through mode for the OpenID Connect authorization endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerOwinBuilder EnableAuthorizationEndpointPassthrough() + => Configure(options => options.EnableAuthorizationEndpointPassthrough = true); - /// - /// Sets the caching policy used by the authorization endpoint. - /// Note: the specified policy is only used when caching is explicitly enabled. - /// - /// The caching policy. - /// The . - public OpenIddictServerOwinBuilder SetAuthorizationRequestCachingPolicy(DistributedCacheEntryOptions policy) - { - if (policy is null) - { - throw new ArgumentNullException(nameof(policy)); - } + /// + /// Enables error pass-through support, so that the rest of the request processing pipeline is + /// automatically invoked when returning an error from the interactive authorization and logout endpoints. + /// When this option is enabled, special logic must be added to these actions to handle errors, that can be + /// retrieved using + /// + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictServerOwinBuilder EnableErrorPassthrough() + => Configure(options => options.EnableErrorPassthrough = true); - return Configure(options => options.AuthorizationRequestCachingPolicy = policy); - } + /// + /// Enables the pass-through mode for the OpenID Connect logout endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerOwinBuilder EnableLogoutEndpointPassthrough() + => Configure(options => options.EnableLogoutEndpointPassthrough = true); + + /// + /// Enables the pass-through mode for the OpenID Connect token endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerOwinBuilder EnableTokenEndpointPassthrough() + => Configure(options => options.EnableTokenEndpointPassthrough = true); + + /// + /// Enables the pass-through mode for the OpenID Connect userinfo endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerOwinBuilder EnableUserinfoEndpointPassthrough() + => Configure(options => options.EnableUserinfoEndpointPassthrough = true); - /// - /// Sets the caching policy used by the logout endpoint. - /// Note: the specified policy is only used when caching is explicitly enabled. - /// - /// The caching policy. - /// The . - public OpenIddictServerOwinBuilder SetLogoutRequestCachingPolicy(DistributedCacheEntryOptions policy) + /// + /// Enables the pass-through mode for the OpenID Connect user verification endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + /// The . + public OpenIddictServerOwinBuilder EnableVerificationEndpointPassthrough() + => Configure(options => options.EnableVerificationEndpointPassthrough = true); + + /// + /// Enables authorization request caching, so that authorization requests + /// are automatically stored in the distributed cache, which allows flowing + /// large payloads across requests. Enabling this option is recommended + /// when using external authentication providers or when large GET or POST + /// OpenID Connect authorization requests support is required. + /// + /// The . + public OpenIddictServerOwinBuilder EnableAuthorizationRequestCaching() + => Configure(options => options.EnableAuthorizationRequestCaching = true); + + /// + /// Enables logout request caching, so that logout requests + /// are automatically stored in the distributed cache. + /// + /// The . + public OpenIddictServerOwinBuilder EnableLogoutRequestCaching() + => Configure(options => options.EnableLogoutRequestCaching = true); + + /// + /// Sets the realm returned to the caller as part of the WWW-Authenticate header. + /// + /// The issuer address. + /// The . + public OpenIddictServerOwinBuilder SetRealm(string realm) + { + if (string.IsNullOrEmpty(realm)) { - if (policy is null) - { - throw new ArgumentNullException(nameof(policy)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0107), nameof(realm)); + } + + return Configure(options => options.Realm = realm); + } - return Configure(options => options.LogoutRequestCachingPolicy = policy); + /// + /// Sets the caching policy used by the authorization endpoint. + /// Note: the specified policy is only used when caching is explicitly enabled. + /// + /// The caching policy. + /// The . + public OpenIddictServerOwinBuilder SetAuthorizationRequestCachingPolicy(DistributedCacheEntryOptions policy) + { + if (policy is null) + { + throw new ArgumentNullException(nameof(policy)); } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + return Configure(options => options.AuthorizationRequestCachingPolicy = policy); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + /// Sets the caching policy used by the logout endpoint. + /// Note: the specified policy is only used when caching is explicitly enabled. + /// + /// The caching policy. + /// The . + public OpenIddictServerOwinBuilder SetLogoutRequestCachingPolicy(DistributedCacheEntryOptions policy) + { + if (policy is null) + { + throw new ArgumentNullException(nameof(policy)); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); + return Configure(options => options.LogoutRequestCachingPolicy = policy); } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs index 199d9868..ce6ca787 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs @@ -9,36 +9,35 @@ using Microsoft.Extensions.Options; using Microsoft.Owin.Security; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +/// +/// Contains the methods required to ensure that the OpenIddict server configuration is valid. +/// +public class OpenIddictServerOwinConfiguration : IConfigureOptions, + IPostConfigureOptions { - /// - /// Contains the methods required to ensure that the OpenIddict server configuration is valid. - /// - public class OpenIddictServerOwinConfiguration : IConfigureOptions, - IPostConfigureOptions + public void Configure(OpenIddictServerOptions options) { - public void Configure(OpenIddictServerOptions options) + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - // Register the built-in event handlers used by the OpenIddict OWIN server components. - options.Handlers.AddRange(OpenIddictServerOwinHandlers.DefaultHandlers); + throw new ArgumentNullException(nameof(options)); } - public void PostConfigure(string name, OpenIddictServerOwinOptions options) + // Register the built-in event handlers used by the OpenIddict OWIN server components. + options.Handlers.AddRange(OpenIddictServerOwinHandlers.DefaultHandlers); + } + + public void PostConfigure(string name, OpenIddictServerOwinOptions options) + { + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new ArgumentNullException(nameof(options)); + } - if (options.AuthenticationMode == AuthenticationMode.Active) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0119)); - } + if (options.AuthenticationMode == AuthenticationMode.Active) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0119)); } } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs index e51b547e..7f37f51e 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs @@ -4,51 +4,50 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +/// +/// Exposes common constants used by the OpenIddict OWIN host. +/// +public static class OpenIddictServerOwinConstants { - /// - /// Exposes common constants used by the OpenIddict OWIN host. - /// - public static class OpenIddictServerOwinConstants + public static class Cache { - public static class Cache - { - public const string AuthorizationRequest = "openiddict-authorization-request:"; - public const string LogoutRequest = "openiddict-logout-request:"; - } + public const string AuthorizationRequest = "openiddict-authorization-request:"; + public const string LogoutRequest = "openiddict-logout-request:"; + } - public static class Headers - { - public const string Authorization = "Authorization"; - public const string CacheControl = "Cache-Control"; - public const string ContentType = "Content-Type"; - public const string Expires = "Expires"; - public const string Host = "Host"; - public const string Pragma = "Pragma"; - public const string WwwAuthenticate = "WWW-Authenticate"; - } + public static class Headers + { + public const string Authorization = "Authorization"; + public const string CacheControl = "Cache-Control"; + public const string ContentType = "Content-Type"; + public const string Expires = "Expires"; + public const string Host = "Host"; + public const string Pragma = "Pragma"; + public const string WwwAuthenticate = "WWW-Authenticate"; + } - public static class JsonWebTokenTypes + public static class JsonWebTokenTypes + { + public static class Private { - public static class Private - { - public const string AuthorizationRequest = "oi_authrq+jwt"; - public const string LogoutRequest = "oi_lgtrq+jwt"; - } + public const string AuthorizationRequest = "oi_authrq+jwt"; + public const string LogoutRequest = "oi_lgtrq+jwt"; } + } - public static class Properties - { - public const string AccessTokenPrincipal = ".access_token_principal"; - public const string AuthorizationCodePrincipal = ".authorization_code_principal"; - public const string DeviceCodePrincipal = ".device_code_principal"; - public const string Error = ".error"; - public const string ErrorDescription = ".error_description"; - public const string ErrorUri = ".error_uri"; - public const string IdentityTokenPrincipal = ".identity_token_principal"; - public const string RefreshTokenPrincipal = ".refresh_token_principal"; - public const string Scope = ".scope"; - public const string UserCodePrincipal = ".user_code_principal"; - } + public static class Properties + { + public const string AccessTokenPrincipal = ".access_token_principal"; + public const string AuthorizationCodePrincipal = ".authorization_code_principal"; + public const string DeviceCodePrincipal = ".device_code_principal"; + public const string Error = ".error"; + public const string ErrorDescription = ".error_description"; + public const string ErrorUri = ".error_uri"; + public const string IdentityTokenPrincipal = ".identity_token_principal"; + public const string RefreshTokenPrincipal = ".refresh_token_principal"; + public const string Scope = ".scope"; + public const string UserCodePrincipal = ".user_code_principal"; } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinDefaults.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinDefaults.cs index 08308c18..83de9fed 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinDefaults.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinDefaults.cs @@ -6,16 +6,15 @@ using Microsoft.Owin.Security; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +/// +/// Exposes the default values used by the OpenIddict server handler. +/// +public static class OpenIddictServerOwinDefaults { /// - /// Exposes the default values used by the OpenIddict server handler. + /// Default value for . /// - public static class OpenIddictServerOwinDefaults - { - /// - /// Default value for . - /// - public const string AuthenticationType = "OpenIddict.Server.Owin"; - } + public const string AuthenticationType = "OpenIddict.Server.Owin"; } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinExtensions.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinExtensions.cs index cc99dff1..fe3d1385 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinExtensions.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinExtensions.cs @@ -13,83 +13,82 @@ using OpenIddict.Server.Owin; using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters; using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlers; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict server services. +/// +public static class OpenIddictServerOwinExtensions { /// - /// Exposes extensions allowing to register the OpenIddict server services. + /// Registers the OpenIddict server services for OWIN in the DI container. /// - public static class OpenIddictServerOwinExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerOwinBuilder UseOwin(this OpenIddictServerBuilder builder) { - /// - /// Registers the OpenIddict server services for OWIN in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictServerOwinBuilder UseOwin(this OpenIddictServerBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddWebEncoders(); + builder.Services.AddWebEncoders(); - // Note: unlike regular OWIN middleware, the OpenIddict server middleware is registered - // as a scoped service in the DI container. This allows containers that support middleware - // resolution (like Autofac) to use it without requiring additional configuration. - builder.Services.TryAddScoped(); + // Note: unlike regular OWIN middleware, the OpenIddict server middleware is registered + // as a scoped service in the DI container. This allows containers that support middleware + // resolution (like Autofac) to use it without requiring additional configuration. + builder.Services.TryAddScoped(); - // Register the built-in event handlers used by the OpenIddict OWIN server components. - // Note: the order used here is not important, as the actual order is set in the options. - builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); + // Register the built-in event handlers used by the OpenIddict OWIN server components. + // Note: the order used here is not important, as the actual order is set in the options. + builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); - // Register the built-in filters used by the default OpenIddict OWIN server event handlers. - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); + // Register the built-in filters used by the default OpenIddict OWIN server event handlers. + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); - // Register the option initializers used by the OpenIddict OWIN server integration services. - // Note: TryAddEnumerable() is used here to ensure the initializers are only registered once. - builder.Services.TryAddEnumerable(new[] - { - ServiceDescriptor.Singleton, OpenIddictServerOwinConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictServerOwinConfiguration>() - }); + // Register the option initializers used by the OpenIddict OWIN server integration services. + // Note: TryAddEnumerable() is used here to ensure the initializers are only registered once. + builder.Services.TryAddEnumerable(new[] + { + ServiceDescriptor.Singleton, OpenIddictServerOwinConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictServerOwinConfiguration>() + }); - return new OpenIddictServerOwinBuilder(builder.Services); - } + return new OpenIddictServerOwinBuilder(builder.Services); + } - /// - /// Registers the OpenIddict server services for OWIN in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the server services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictServerBuilder UseOwin( - this OpenIddictServerBuilder builder, Action configuration) + /// + /// Registers the OpenIddict server services for OWIN in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the server services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerBuilder UseOwin( + this OpenIddictServerBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseOwin()); + configuration(builder.UseOwin()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs index 6002c097..03cb12c3 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs @@ -17,370 +17,369 @@ using static OpenIddict.Server.OpenIddictServerEvents; using Properties = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +/// +/// Provides the entry point necessary to register the OpenIddict server in an OWIN pipeline. +/// +public class OpenIddictServerOwinHandler : AuthenticationHandler { + private readonly IOpenIddictServerDispatcher _dispatcher; + private readonly IOpenIddictServerFactory _factory; + /// - /// Provides the entry point necessary to register the OpenIddict server in an OWIN pipeline. + /// Creates a new instance of the class. /// - public class OpenIddictServerOwinHandler : AuthenticationHandler + /// The OpenIddict server dispatcher used by this instance. + /// The OpenIddict server factory used by this instance. + public OpenIddictServerOwinHandler( + IOpenIddictServerDispatcher dispatcher, + IOpenIddictServerFactory factory) + { + _dispatcher = dispatcher; + _factory = factory; + } + + /// + protected override async Task InitializeCoreAsync() { - private readonly IOpenIddictServerDispatcher _dispatcher; - private readonly IOpenIddictServerFactory _factory; - - /// - /// Creates a new instance of the class. - /// - /// The OpenIddict server dispatcher used by this instance. - /// The OpenIddict server factory used by this instance. - public OpenIddictServerOwinHandler( - IOpenIddictServerDispatcher dispatcher, - IOpenIddictServerFactory factory) + // Note: the transaction may be already attached when replaying an OWIN request + // (e.g when using a status code pages middleware re-invoking the OWIN pipeline). + var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName); + if (transaction is null) { - _dispatcher = dispatcher; - _factory = factory; + // Create a new transaction and attach the OWIN request to make it available to the OWIN handlers. + transaction = await _factory.CreateTransactionAsync(); + transaction.Properties[typeof(IOwinRequest).FullName!] = new WeakReference(Request); + + // Attach the OpenIddict server transaction to the OWIN shared dictionary + // so that it can retrieved while performing sign-in/sign-out operations. + Context.Set(typeof(OpenIddictServerTransaction).FullName, transaction); } - /// - protected override async Task InitializeCoreAsync() - { - // Note: the transaction may be already attached when replaying an OWIN request - // (e.g when using a status code pages middleware re-invoking the OWIN pipeline). - var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName); - if (transaction is null) - { - // Create a new transaction and attach the OWIN request to make it available to the OWIN handlers. - transaction = await _factory.CreateTransactionAsync(); - transaction.Properties[typeof(IOwinRequest).FullName!] = new WeakReference(Request); + var context = new ProcessRequestContext(transaction); + await _dispatcher.DispatchAsync(context); - // Attach the OpenIddict server transaction to the OWIN shared dictionary - // so that it can retrieved while performing sign-in/sign-out operations. - Context.Set(typeof(OpenIddictServerTransaction).FullName, transaction); - } + // Store the context in the transaction so that it can be retrieved from InvokeAsync(). + transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context); + } - var context = new ProcessRequestContext(transaction); - await _dispatcher.DispatchAsync(context); + /// + public override async Task InvokeAsync() + { + // Note: due to internal differences between ASP.NET Core and Katana, the request MUST start being processed + // in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when + // active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case. + + var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + + var context = transaction.GetProperty(typeof(ProcessRequestContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); - // Store the context in the transaction so that it can be retrieved from InvokeAsync(). - transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context); + if (context.IsRequestHandled) + { + return true; } - /// - public override async Task InvokeAsync() + else if (context.IsRequestSkipped) { - // Note: due to internal differences between ASP.NET Core and Katana, the request MUST start being processed - // in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when - // active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case. + return false; + } - var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + else if (context.IsRejected) + { + var notification = new ProcessErrorContext(transaction) + { + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, + Response = new OpenIddictResponse() + }; - var context = transaction.GetProperty(typeof(ProcessRequestContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + await _dispatcher.DispatchAsync(notification); - if (context.IsRequestHandled) + if (notification.IsRequestHandled) { return true; } - else if (context.IsRequestSkipped) + else if (notification.IsRequestSkipped) { return false; } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; + } - await _dispatcher.DispatchAsync(notification); + return false; + } - if (notification.IsRequestHandled) - { - return true; - } + /// + protected override async Task AuthenticateCoreAsync() + { + var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName); + if (transaction is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + } - else if (notification.IsRequestSkipped) - { - return false; - } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); + // Note: in many cases, the authentication token was already validated by the time this action is called + // (generally later in the pipeline, when using the pass-through mode). To avoid having to re-validate it, + // the authentication context is resolved from the transaction. If it's not available, a new one is created. + var context = transaction.GetProperty(typeof(ProcessAuthenticationContext).FullName!); + if (context is null) + { + context = new ProcessAuthenticationContext(transaction); + await _dispatcher.DispatchAsync(context); - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the authentication result without triggering a new authentication flow. + transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, context); + } - return false; + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return null; } - /// - protected override async Task AuthenticateCoreAsync() + else if (context.IsRejected) { - var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName); - if (transaction is null) + // Note: the missing_token error is special-cased to indicate to Katana + // that no authentication result could be produced due to the lack of token. + // This also helps reducing the logging noise when no token is specified. + if (string.Equals(context.Error, Errors.MissingToken, StringComparison.Ordinal)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + return null; } - // Note: in many cases, the authentication token was already validated by the time this action is called - // (generally later in the pipeline, when using the pass-through mode). To avoid having to re-validate it, - // the authentication context is resolved from the transaction. If it's not available, a new one is created. - var context = transaction.GetProperty(typeof(ProcessAuthenticationContext).FullName!); - if (context is null) + var properties = new AuthenticationProperties(new Dictionary { - context = new ProcessAuthenticationContext(transaction); - await _dispatcher.DispatchAsync(context); + [Properties.Error] = context.Error, + [Properties.ErrorDescription] = context.ErrorDescription, + [Properties.ErrorUri] = context.ErrorUri + }); - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the authentication result without triggering a new authentication flow. - transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, context); - } + return new AuthenticationTicket(null, properties); + } - if (context.IsRequestHandled || context.IsRequestSkipped) + else + { + // A single main claims-based principal instance can be attached to an authentication ticket. + var principal = context.EndpointType switch + { + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout + => context.IdentityTokenPrincipal, + + OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation + => context.AccessTokenPrincipal ?? + context.RefreshTokenPrincipal ?? + context.IdentityTokenPrincipal ?? + context.AuthorizationCodePrincipal ?? + context.DeviceCodePrincipal ?? + context.UserCodePrincipal, + + OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + => context.AuthorizationCodePrincipal, + OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + => context.DeviceCodePrincipal, + OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + => context.RefreshTokenPrincipal, + + OpenIddictServerEndpointType.Userinfo => context.AccessTokenPrincipal, + + OpenIddictServerEndpointType.Verification => context.UserCodePrincipal, + + _ => null + }; + + if (principal is null) { return null; } - else if (context.IsRejected) + var properties = new AuthenticationProperties { - // Note: the missing_token error is special-cased to indicate to Katana - // that no authentication result could be produced due to the lack of token. - // This also helps reducing the logging noise when no token is specified. - if (string.Equals(context.Error, Errors.MissingToken, StringComparison.Ordinal)) - { - return null; - } + ExpiresUtc = principal.GetExpirationDate(), + IssuedUtc = principal.GetCreationDate() + }; - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); + // Attach the tokens to allow any ASP.NET Core component (e.g a controller) + // to retrieve them (e.g to make an API request to another application). - return new AuthenticationTicket(null, properties); + if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken)) + { + properties.Dictionary[TokenTypeHints.AccessToken] = context.AccessToken; } - else + if (context.AuthorizationCodePrincipal is not null && !string.IsNullOrEmpty(context.AuthorizationCode)) { - // A single main claims-based principal instance can be attached to an authentication ticket. - var principal = context.EndpointType switch - { - OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout - => context.IdentityTokenPrincipal, - - OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation - => context.AccessTokenPrincipal ?? - context.RefreshTokenPrincipal ?? - context.IdentityTokenPrincipal ?? - context.AuthorizationCodePrincipal ?? - context.DeviceCodePrincipal ?? - context.UserCodePrincipal, + properties.Dictionary[TokenTypeHints.AuthorizationCode] = context.AuthorizationCode; + } - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => context.AuthorizationCodePrincipal, - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => context.DeviceCodePrincipal, - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => context.RefreshTokenPrincipal, + if (context.DeviceCodePrincipal is not null && !string.IsNullOrEmpty(context.DeviceCode)) + { + properties.Dictionary[TokenTypeHints.DeviceCode] = context.DeviceCode; + } - OpenIddictServerEndpointType.Userinfo => context.AccessTokenPrincipal, + if (context.IdentityTokenPrincipal is not null && !string.IsNullOrEmpty(context.IdentityToken)) + { + properties.Dictionary[TokenTypeHints.IdToken] = context.IdentityToken; + } - OpenIddictServerEndpointType.Verification => context.UserCodePrincipal, + if (context.RefreshTokenPrincipal is not null && !string.IsNullOrEmpty(context.RefreshToken)) + { + properties.Dictionary[TokenTypeHints.RefreshToken] = context.RefreshToken; + } - _ => null - }; + if (context.UserCodePrincipal is not null && !string.IsNullOrEmpty(context.UserCode)) + { + properties.Dictionary[TokenTypeHints.UserCode] = context.UserCode; + } - if (principal is null) - { - return null; - } + return new AuthenticationTicket((ClaimsIdentity) principal.Identity, properties); + } + } - var properties = new AuthenticationProperties - { - ExpiresUtc = principal.GetExpirationDate(), - IssuedUtc = principal.GetCreationDate() - }; + /// + protected override async Task TeardownCoreAsync() + { + // Note: OWIN authentication handlers cannot reliabily write to the response stream + // from ApplyResponseGrantAsync or ApplyResponseChallengeAsync because these methods + // are susceptible to be invoked from AuthenticationHandler.OnSendingHeaderCallback, + // where calling Write or WriteAsync on the response stream may result in a deadlock + // on hosts using streamed responses. To work around this limitation, this handler + // doesn't implement ApplyResponseGrantAsync but TeardownCoreAsync, which is never called + // by AuthenticationHandler.OnSendingHeaderCallback. In theory, this would prevent + // OpenIddictServerOwinMiddleware from both applying the response grant and allowing + // the next middleware in the pipeline to alter the response stream but in practice, + // OpenIddictServerOwinMiddleware is assumed to be the only middleware allowed to write + // to the response stream when a response grant (sign-in/out or challenge) was applied. + + // Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code + // corresponds to a challenge response, as LookupChallenge() will always return a non-null + // value when active authentication is used, even if no challenge was actually triggered. + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + if (challenge is not null && Response.StatusCode is 401 or 403) + { + var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); - // Attach the tokens to allow any ASP.NET Core component (e.g a controller) - // to retrieve them (e.g to make an API request to another application). + transaction.Properties[typeof(AuthenticationProperties).FullName!] = challenge.Properties ?? new AuthenticationProperties(); - if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken)) - { - properties.Dictionary[TokenTypeHints.AccessToken] = context.AccessToken; - } + var context = new ProcessChallengeContext(transaction) + { + Response = new OpenIddictResponse() + }; - if (context.AuthorizationCodePrincipal is not null && !string.IsNullOrEmpty(context.AuthorizationCode)) - { - properties.Dictionary[TokenTypeHints.AuthorizationCode] = context.AuthorizationCode; - } + await _dispatcher.DispatchAsync(context); - if (context.DeviceCodePrincipal is not null && !string.IsNullOrEmpty(context.DeviceCode)) - { - properties.Dictionary[TokenTypeHints.DeviceCode] = context.DeviceCode; - } + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return; + } - if (context.IdentityTokenPrincipal is not null && !string.IsNullOrEmpty(context.IdentityToken)) + else if (context.IsRejected) + { + var notification = new ProcessErrorContext(transaction) { - properties.Dictionary[TokenTypeHints.IdToken] = context.IdentityToken; - } + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, + Response = new OpenIddictResponse() + }; - if (context.RefreshTokenPrincipal is not null && !string.IsNullOrEmpty(context.RefreshToken)) - { - properties.Dictionary[TokenTypeHints.RefreshToken] = context.RefreshToken; - } + await _dispatcher.DispatchAsync(notification); - if (context.UserCodePrincipal is not null && !string.IsNullOrEmpty(context.UserCode)) + if (notification.IsRequestHandled || context.IsRequestSkipped) { - properties.Dictionary[TokenTypeHints.UserCode] = context.UserCode; + return; } - return new AuthenticationTicket((ClaimsIdentity) principal.Identity, properties); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); } } - /// - protected override async Task TeardownCoreAsync() + var signin = Helper.LookupSignIn(Options.AuthenticationType); + if (signin is not null) { - // Note: OWIN authentication handlers cannot reliabily write to the response stream - // from ApplyResponseGrantAsync or ApplyResponseChallengeAsync because these methods - // are susceptible to be invoked from AuthenticationHandler.OnSendingHeaderCallback, - // where calling Write or WriteAsync on the response stream may result in a deadlock - // on hosts using streamed responses. To work around this limitation, this handler - // doesn't implement ApplyResponseGrantAsync but TeardownCoreAsync, which is never called - // by AuthenticationHandler.OnSendingHeaderCallback. In theory, this would prevent - // OpenIddictServerOwinMiddleware from both applying the response grant and allowing - // the next middleware in the pipeline to alter the response stream but in practice, - // OpenIddictServerOwinMiddleware is assumed to be the only middleware allowed to write - // to the response stream when a response grant (sign-in/out or challenge) was applied. - - // Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code - // corresponds to a challenge response, as LookupChallenge() will always return a non-null - // value when active authentication is used, even if no challenge was actually triggered. - var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge is not null && Response.StatusCode is 401 or 403) - { - var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); - - transaction.Properties[typeof(AuthenticationProperties).FullName!] = challenge.Properties ?? new AuthenticationProperties(); + var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); - var context = new ProcessChallengeContext(transaction) - { - Response = new OpenIddictResponse() - }; + transaction.Properties[typeof(AuthenticationProperties).FullName!] = signin.Properties ?? new AuthenticationProperties(); - await _dispatcher.DispatchAsync(context); + var context = new ProcessSignInContext(transaction) + { + Principal = signin.Principal, + Response = new OpenIddictResponse() + }; - if (context.IsRequestHandled || context.IsRequestSkipped) - { - return; - } + await _dispatcher.DispatchAsync(context); - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; - - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled || context.IsRequestSkipped) - { - return; - } - - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - } + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return; } - var signin = Helper.LookupSignIn(Options.AuthenticationType); - if (signin is not null) + else if (context.IsRejected) { - var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); - - transaction.Properties[typeof(AuthenticationProperties).FullName!] = signin.Properties ?? new AuthenticationProperties(); - - var context = new ProcessSignInContext(transaction) + var notification = new ProcessErrorContext(transaction) { - Principal = signin.Principal, + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, Response = new OpenIddictResponse() }; - await _dispatcher.DispatchAsync(context); + await _dispatcher.DispatchAsync(notification); - if (context.IsRequestHandled || context.IsRequestSkipped) + if (notification.IsRequestHandled || context.IsRequestSkipped) { return; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; - - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled || context.IsRequestSkipped) - { - return; - } - - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); } + } - var signout = Helper.LookupSignOut(Options.AuthenticationType, Options.AuthenticationMode); - if (signout is not null) + var signout = Helper.LookupSignOut(Options.AuthenticationType, Options.AuthenticationMode); + if (signout is not null) + { + var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + + transaction.Properties[typeof(AuthenticationProperties).FullName!] = signout.Properties ?? new AuthenticationProperties(); + + var context = new ProcessSignOutContext(transaction) { - var transaction = Context.Get(typeof(OpenIddictServerTransaction).FullName) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0112)); + Response = new OpenIddictResponse() + }; - transaction.Properties[typeof(AuthenticationProperties).FullName!] = signout.Properties ?? new AuthenticationProperties(); + await _dispatcher.DispatchAsync(context); + + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return; + } - var context = new ProcessSignOutContext(transaction) + else if (context.IsRejected) + { + var notification = new ProcessErrorContext(transaction) { + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, Response = new OpenIddictResponse() }; - await _dispatcher.DispatchAsync(context); + await _dispatcher.DispatchAsync(notification); - if (context.IsRequestHandled || context.IsRequestSkipped) + if (notification.IsRequestHandled || context.IsRequestSkipped) { return; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; - - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled || context.IsRequestSkipped) - { - return; - } - - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); } } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlerFilters.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlerFilters.cs index b2a26f1f..d6d22f7f 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlerFilters.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlerFilters.cs @@ -10,221 +10,220 @@ using Microsoft.Extensions.Options; using Owin; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +/// +/// Contains a collection of event handler filters commonly used by the OWIN handlers. +/// +public static class OpenIddictServerOwinHandlerFilters { /// - /// Contains a collection of event handler filters commonly used by the OWIN handlers. + /// Represents a filter that excludes the associated handlers if authorization request caching was not enabled. /// - public static class OpenIddictServerOwinHandlerFilters + public class RequireAuthorizationRequestCachingEnabled : IOpenIddictServerHandlerFilter { - /// - /// Represents a filter that excludes the associated handlers if authorization request caching was not enabled. - /// - public class RequireAuthorizationRequestCachingEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + private readonly IOptionsMonitor _options; - public RequireAuthorizationRequestCachingEnabled(IOptionsMonitor options) - => _options = options; + public RequireAuthorizationRequestCachingEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableAuthorizationRequestCaching); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableAuthorizationRequestCaching); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the authorization endpoint. - /// - public class RequireAuthorizationEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the authorization endpoint. + /// + public class RequireAuthorizationEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireAuthorizationEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireAuthorizationEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableAuthorizationEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableAuthorizationEndpointPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if error pass-through was not enabled. - /// - public class RequireErrorPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if error pass-through was not enabled. + /// + public class RequireErrorPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireErrorPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireErrorPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableErrorPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableErrorPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if logout request caching was not enabled. - /// - public class RequireLogoutRequestCachingEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if logout request caching was not enabled. + /// + public class RequireLogoutRequestCachingEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireLogoutRequestCachingEnabled(IOptionsMonitor options) - => _options = options; + public RequireLogoutRequestCachingEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableLogoutRequestCaching); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableLogoutRequestCaching); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the logout endpoint. - /// - public class RequireLogoutEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the logout endpoint. + /// + public class RequireLogoutEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireLogoutEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireLogoutEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableLogoutEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableLogoutEndpointPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if no OWIN request can be found. - /// - public class RequireOwinRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no OWIN request can be found. + /// + public class RequireOwinRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Transaction.GetOwinRequest() is not null); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Transaction.GetOwinRequest() is not null); } + } - /// - /// Represents a filter that excludes the associated handlers if the HTTPS requirement was disabled. - /// - public class RequireTransportSecurityRequirementEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the HTTPS requirement was disabled. + /// + public class RequireTransportSecurityRequirementEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireTransportSecurityRequirementEnabled(IOptionsMonitor options) - => _options = options; + public RequireTransportSecurityRequirementEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!_options.CurrentValue.DisableTransportSecurityRequirement); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!_options.CurrentValue.DisableTransportSecurityRequirement); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the authorization endpoint. - /// - public class RequireTokenEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the authorization endpoint. + /// + public class RequireTokenEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireTokenEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireTokenEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableTokenEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableTokenEndpointPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the userinfo endpoint. - /// - public class RequireUserinfoEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the userinfo endpoint. + /// + public class RequireUserinfoEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireUserinfoEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireUserinfoEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableUserinfoEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableUserinfoEndpointPassthrough); } + } - /// - /// Represents a filter that excludes the associated handlers if the - /// pass-through mode was not enabled for the verification endpoint. - /// - public class RequireVerificationEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter - { - private readonly IOptionsMonitor _options; + /// + /// Represents a filter that excludes the associated handlers if the + /// pass-through mode was not enabled for the verification endpoint. + /// + public class RequireVerificationEndpointPassthroughEnabled : IOpenIddictServerHandlerFilter + { + private readonly IOptionsMonitor _options; - public RequireVerificationEndpointPassthroughEnabled(IOptionsMonitor options) - => _options = options; + public RequireVerificationEndpointPassthroughEnabled(IOptionsMonitor options) + => _options = options; - public ValueTask IsActiveAsync(BaseContext context) + public ValueTask IsActiveAsync(BaseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(_options.CurrentValue.EnableVerificationEndpointPassthrough); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(_options.CurrentValue.EnableVerificationEndpointPassthrough); } } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs index 5e5cedfb..2e31a7c8 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs @@ -30,416 +30,336 @@ using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters; using JsonWebTokenTypes = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.JsonWebTokenTypes; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +public static partial class OpenIddictServerOwinHandlers { - public static partial class OpenIddictServerOwinHandlers + public static class Authentication { - public static class Authentication + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Authorization request extraction: + */ + ExtractGetOrPostRequest.Descriptor, + RestoreCachedRequestParameters.Descriptor, + CacheRequestParameters.Descriptor, + + /* + * Authorization request handling: + */ + EnablePassthroughMode.Descriptor, + + /* + * Authorization response processing: + */ + RemoveCachedRequest.Descriptor, + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + ProcessFormPostResponse.Descriptor, + ProcessQueryResponse.Descriptor, + ProcessFragmentResponse.Descriptor, + ProcessPassthroughErrorResponse.Descriptor, + ProcessLocalErrorResponse.Descriptor); + + /// + /// Contains the logic responsible of restoring cached requests from the request_id, if specified. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class RestoreCachedRequestParameters : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Authorization request extraction: - */ - ExtractGetOrPostRequest.Descriptor, - RestoreCachedRequestParameters.Descriptor, - CacheRequestParameters.Descriptor, - - /* - * Authorization request handling: - */ - EnablePassthroughMode.Descriptor, - - /* - * Authorization response processing: - */ - RemoveCachedRequest.Descriptor, - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - ProcessFormPostResponse.Descriptor, - ProcessQueryResponse.Descriptor, - ProcessFragmentResponse.Descriptor, - ProcessPassthroughErrorResponse.Descriptor, - ProcessLocalErrorResponse.Descriptor); + private readonly IDistributedCache _cache; + + public RestoreCachedRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public RestoreCachedRequestParameters(IDistributedCache cache) + => _cache = cache; /// - /// Contains the logic responsible of restoring cached requests from the request_id, if specified. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class RestoreCachedRequestParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ExtractAuthorizationRequestContext context) { - private readonly IDistributedCache _cache; - - public RestoreCachedRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - - public RestoreCachedRequestParameters(IDistributedCache cache) - => _cache = cache; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ExtractAuthorizationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); + Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - // If a request_id parameter can be found in the authorization request, - // restore the complete authorization request from the distributed cache. + // If a request_id parameter can be found in the authorization request, + // restore the complete authorization request from the distributed cache. - if (string.IsNullOrEmpty(context.Request.RequestId)) - { - return; - } + if (string.IsNullOrEmpty(context.Request.RequestId)) + { + return; + } - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - var token = await _cache.GetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId); - if (token is null || !context.Options.JsonWebTokenHandler.CanReadToken(token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6146), Parameters.RequestId); + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + var token = await _cache.GetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId); + if (token is null || !context.Options.JsonWebTokenHandler.CanReadToken(token)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6146), Parameters.RequestId); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.RequestId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.RequestId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } - var parameters = context.Options.TokenValidationParameters.Clone(); - parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; - parameters.ValidAudience = context.Issuer?.AbsoluteUri; - parameters.ValidTypes = new[] { JsonWebTokenTypes.Private.AuthorizationRequest }; + var parameters = context.Options.TokenValidationParameters.Clone(); + parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; + parameters.ValidAudience = context.Issuer?.AbsoluteUri; + parameters.ValidTypes = new[] { JsonWebTokenTypes.Private.AuthorizationRequest }; - var result = context.Options.JsonWebTokenHandler.ValidateToken(token, parameters); - if (!result.IsValid) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6146), Parameters.RequestId); + var result = context.Options.JsonWebTokenHandler.ValidateToken(token, parameters); + if (!result.IsValid) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6146), Parameters.RequestId); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.RequestId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.RequestId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } - using var document = JsonDocument.Parse( - Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload)); - if (document.RootElement.ValueKind != JsonValueKind.Object) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0117)); - } + using var document = JsonDocument.Parse( + Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload)); + if (document.RootElement.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0117)); + } - // Restore the authorization request parameters from the serialized payload. - foreach (var parameter in document.RootElement.EnumerateObject()) + // Restore the authorization request parameters from the serialized payload. + foreach (var parameter in document.RootElement.EnumerateObject()) + { + // Avoid overriding the current request parameters. + if (context.Request.HasParameter(parameter.Name)) { - // Avoid overriding the current request parameters. - if (context.Request.HasParameter(parameter.Name)) - { - continue; - } - - context.Request.SetParameter(parameter.Name, parameter.Value.Clone()); + continue; } + + context.Request.SetParameter(parameter.Name, parameter.Value.Clone()); } } + } + + /// + /// Contains the logic responsible of caching authorization requests, if applicable. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class CacheRequestParameters : IOpenIddictServerHandler + { + private readonly IDistributedCache _cache; + private readonly IOptionsMonitor _options; + + public CacheRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public CacheRequestParameters( + IDistributedCache cache, + IOptionsMonitor options) + { + _cache = cache; + _options = options; + } /// - /// Contains the logic responsible of caching authorization requests, if applicable. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class CacheRequestParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(RestoreCachedRequestParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ExtractAuthorizationRequestContext context) { - private readonly IDistributedCache _cache; - private readonly IOptionsMonitor _options; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public CacheRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - public CacheRequestParameters( - IDistributedCache cache, - IOptionsMonitor options) + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) { - _cache = cache; - _options = options; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); } - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(RestoreCachedRequestParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ExtractAuthorizationRequestContext context) + // Don't cache the request if the request doesn't include any parameter. + // If a request_id parameter can be found in the authorization request, + // ignore the following logic to prevent an infinite redirect loop. + if (context.Request.Count == 0 || !string.IsNullOrEmpty(context.Request.RequestId)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return; + } - Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); + // Generate a 256-bit request identifier using a crypto-secure random number generator. + var data = new byte[256 / 8]; + using var generator = RandomNumberGenerator.Create(); + generator.GetBytes(data); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + context.Request.RequestId = Base64UrlEncoder.Encode(data); - // Don't cache the request if the request doesn't include any parameter. - // If a request_id parameter can be found in the authorization request, - // ignore the following logic to prevent an infinite redirect loop. - if (context.Request.Count == 0 || !string.IsNullOrEmpty(context.Request.RequestId)) - { - return; - } + // Store the serialized authorization request parameters in the distributed cache. + var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor + { + Audience = context.Issuer?.AbsoluteUri, + Claims = context.Request.GetParameters().ToDictionary( + parameter => parameter.Key, + parameter => parameter.Value.Value), + EncryptingCredentials = context.Options.EncryptionCredentials.First(), + Issuer = context.Issuer?.AbsoluteUri, + SigningCredentials = context.Options.SigningCredentials.First(), + Subject = new ClaimsIdentity(), + TokenType = JsonWebTokenTypes.Private.AuthorizationRequest + }); + + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + await _cache.SetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId, + token, _options.CurrentValue.AuthorizationRequestCachingPolicy); + + // Create a new GET authorization request containing only the request_id parameter. + var address = WebUtilities.AddQueryString( + uri: request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase + request.Path, + name: Parameters.RequestId, + value: context.Request.RequestId); + + request.Context.Response.Redirect(address); + + // Mark the response as handled to skip the rest of the pipeline. + context.HandleRequest(); + } + } - // Generate a 256-bit request identifier using a crypto-secure random number generator. - var data = new byte[256 / 8]; - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); + /// + /// Contains the logic responsible of removing cached authorization requests from the distributed cache. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class RemoveCachedRequest : IOpenIddictServerHandler + { + private readonly IDistributedCache _cache; - context.Request.RequestId = Base64UrlEncoder.Encode(data); + public RemoveCachedRequest() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - // Store the serialized authorization request parameters in the distributed cache. - var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor - { - Audience = context.Issuer?.AbsoluteUri, - Claims = context.Request.GetParameters().ToDictionary( - parameter => parameter.Key, - parameter => parameter.Value.Value), - EncryptingCredentials = context.Options.EncryptionCredentials.First(), - Issuer = context.Issuer?.AbsoluteUri, - SigningCredentials = context.Options.SigningCredentials.First(), - Subject = new ClaimsIdentity(), - TokenType = JsonWebTokenTypes.Private.AuthorizationRequest - }); - - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - await _cache.SetStringAsync(Cache.AuthorizationRequest + context.Request.RequestId, - token, _options.CurrentValue.AuthorizationRequestCachingPolicy); - - // Create a new GET authorization request containing only the request_id parameter. - var address = WebUtilities.AddQueryString( - uri: request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase + request.Path, - name: Parameters.RequestId, - value: context.Request.RequestId); - - request.Context.Response.Redirect(address); - - // Mark the response as handled to skip the rest of the pipeline. - context.HandleRequest(); - } - } + public RemoveCachedRequest(IDistributedCache cache) + => _cache = cache; /// - /// Contains the logic responsible of removing cached authorization requests from the distributed cache. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class RemoveCachedRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) { - private readonly IDistributedCache _cache; - - public RemoveCachedRequest() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - - public RemoveCachedRequest(IDistributedCache cache) - => _cache = cache; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (string.IsNullOrEmpty(context.Request?.RequestId)) - { - return default; - } - - // Note: the ApplyAuthorizationResponse event is called for both successful - // and errored authorization responses but discrimination is not necessary here, - // as the authorization request must be removed from the distributed cache in both cases. - - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - return new ValueTask(_cache.RemoveAsync(Cache.AuthorizationRequest + context.Request.RequestId)); + throw new ArgumentNullException(nameof(context)); } - } - /// - /// Contains the logic responsible of processing authorization responses using the form_post response mode. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. - /// - public class ProcessFormPostResponse : IOpenIddictServerHandler - { - private readonly HtmlEncoder _encoder; - - public ProcessFormPostResponse(HtmlEncoder encoder) - => _encoder = encoder; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(50_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + if (string.IsNullOrEmpty(context.Request?.RequestId)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } - - if (string.IsNullOrEmpty(context.RedirectUri) || - !string.Equals(context.ResponseMode, ResponseModes.FormPost, StringComparison.Ordinal)) - { - return; - } - - context.Logger.LogInformation(SR.GetResourceString(SR.ID6147), context.RedirectUri, context.Response); - - using (var buffer = new MemoryStream()) - using (var writer = new StreamWriter(buffer)) - { - writer.WriteLine(""); - writer.WriteLine(""); - writer.WriteLine(""); - - // While the redirect_uri parameter should be guarded against unknown values - // by OpenIdConnectServerProvider.ValidateAuthorizationRequest, - // it's still safer to encode it to avoid cross-site scripting attacks - // if the authorization server has a relaxed policy concerning redirect URIs. - writer.WriteLine($@"
"); - - // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters - // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. - // For consistency, multiple parameters with the same name are also supported by this endpoint. - foreach (var (key, value) in - from parameter in context.Response.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) - { - writer.WriteLine($@""); - } - - writer.WriteLine(@""); - writer.WriteLine("
"); - writer.WriteLine(""); - writer.WriteLine(""); - writer.WriteLine(""); - writer.Flush(); + return default; + } - response.StatusCode = 200; - response.ContentLength = buffer.Length; - response.ContentType = "text/html;charset=UTF-8"; + // Note: the ApplyAuthorizationResponse event is called for both successful + // and errored authorization responses but discrimination is not necessary here, + // as the authorization request must be removed from the distributed cache in both cases. - response.Headers["Cache-Control"] = "no-cache"; - response.Headers["Pragma"] = "no-cache"; - response.Headers["Expires"] = "-1"; + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + return new ValueTask(_cache.RemoveAsync(Cache.AuthorizationRequest + context.Request.RequestId)); + } + } - buffer.Seek(offset: 0, loc: SeekOrigin.Begin); - await buffer.CopyToAsync(response.Body, 4096); - } + /// + /// Contains the logic responsible of processing authorization responses using the form_post response mode. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessFormPostResponse : IOpenIddictServerHandler + { + private readonly HtmlEncoder _encoder; - context.HandleRequest(); - } - } + public ProcessFormPostResponse(HtmlEncoder encoder) + => _encoder = encoder; /// - /// Contains the logic responsible of processing authorization responses using the query response mode. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessQueryResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(50_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ApplyAuthorizationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessFormPostResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - if (string.IsNullOrEmpty(context.RedirectUri) || - !string.Equals(context.ResponseMode, ResponseModes.Query, StringComparison.Ordinal)) - { - return default; - } + if (string.IsNullOrEmpty(context.RedirectUri) || + !string.Equals(context.ResponseMode, ResponseModes.FormPost, StringComparison.Ordinal)) + { + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6148), context.RedirectUri, context.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6147), context.RedirectUri, context.Response); + + using (var buffer = new MemoryStream()) + using (var writer = new StreamWriter(buffer)) + { + writer.WriteLine(""); + writer.WriteLine(""); + writer.WriteLine(""); - var location = context.RedirectUri; + // While the redirect_uri parameter should be guarded against unknown values + // by OpenIdConnectServerProvider.ValidateAuthorizationRequest, + // it's still safer to encode it to avoid cross-site scripting attacks + // if the authorization server has a relaxed policy concerning redirect URIs. + writer.WriteLine($@"
"); // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. @@ -452,93 +372,172 @@ namespace OpenIddict.Server.Owin where !string.IsNullOrEmpty(value) select (parameter.Key, Value: value)) { - location = WebUtilities.AddQueryString(location, key, value); + writer.WriteLine($@""); } - response.Redirect(location); - context.HandleRequest(); + writer.WriteLine(@""); + writer.WriteLine("
"); + writer.WriteLine(""); + writer.WriteLine(""); + writer.WriteLine(""); + writer.Flush(); - return default; + response.StatusCode = 200; + response.ContentLength = buffer.Length; + response.ContentType = "text/html;charset=UTF-8"; + + response.Headers["Cache-Control"] = "no-cache"; + response.Headers["Pragma"] = "no-cache"; + response.Headers["Expires"] = "-1"; + + buffer.Seek(offset: 0, loc: SeekOrigin.Begin); + await buffer.CopyToAsync(response.Body, 4096); } + + context.HandleRequest(); } + } + /// + /// Contains the logic responsible of processing authorization responses using the query response mode. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessQueryResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing authorization responses using the fragment response mode. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessFragmentResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessFormPostResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessQueryResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - if (string.IsNullOrEmpty(context.RedirectUri) || - !string.Equals(context.ResponseMode, ResponseModes.Fragment, StringComparison.Ordinal)) - { - return default; - } + if (string.IsNullOrEmpty(context.RedirectUri) || + !string.Equals(context.ResponseMode, ResponseModes.Query, StringComparison.Ordinal)) + { + return default; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6149), context.RedirectUri, context.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6148), context.RedirectUri, context.Response); - var builder = new StringBuilder(context.RedirectUri); + var location = context.RedirectUri; - // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters - // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. - // For consistency, multiple parameters with the same name are also supported by this endpoint. - foreach (var (key, value) in - from parameter in context.Response.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) - { - builder.Append(Contains(builder, '#') ? '&' : '#') - .Append(Uri.EscapeDataString(key)) - .Append('=') - .Append(Uri.EscapeDataString(value)); - } + // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters + // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. + // For consistency, multiple parameters with the same name are also supported by this endpoint. + foreach (var (key, value) in + from parameter in context.Response.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select (parameter.Key, Value: value)) + { + location = WebUtilities.AddQueryString(location, key, value); + } + + response.Redirect(location); + context.HandleRequest(); + + return default; + } + } + + /// + /// Contains the logic responsible of processing authorization responses using the fragment response mode. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessFragmentResponse : IOpenIddictServerHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessQueryResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - response.Redirect(builder.ToString()); - context.HandleRequest(); + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } + if (string.IsNullOrEmpty(context.RedirectUri) || + !string.Equals(context.ResponseMode, ResponseModes.Fragment, StringComparison.Ordinal)) + { return default; + } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6149), context.RedirectUri, context.Response); - static bool Contains(StringBuilder builder, char delimiter) + var builder = new StringBuilder(context.RedirectUri); + + // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters + // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. + // For consistency, multiple parameters with the same name are also supported by this endpoint. + foreach (var (key, value) in + from parameter in context.Response.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select (parameter.Key, Value: value)) + { + builder.Append(Contains(builder, '#') ? '&' : '#') + .Append(Uri.EscapeDataString(key)) + .Append('=') + .Append(Uri.EscapeDataString(value)); + } + + response.Redirect(builder.ToString()); + context.HandleRequest(); + + return default; + + static bool Contains(StringBuilder builder, char delimiter) + { + for (var index = 0; index < builder.Length; index++) { - for (var index = 0; index < builder.Length; index++) + if (builder[index] == delimiter) { - if (builder[index] == delimiter) - { - return true; - } + return true; } - - return false; } + + return false; } } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs index 41825267..923443f8 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs @@ -15,100 +15,99 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +public static partial class OpenIddictServerOwinHandlers { - public static partial class OpenIddictServerOwinHandlers + public static class Device { - public static class Device - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Device request extraction: - */ - ExtractPostRequest.Descriptor, - ExtractBasicAuthenticationCredentials.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Device request extraction: + */ + ExtractPostRequest.Descriptor, + ExtractBasicAuthenticationCredentials.Descriptor, - /* - * Device response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor, + /* + * Device response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor, - /* - * Verification request extraction: - */ - ExtractGetOrPostRequest.Descriptor, + /* + * Verification request extraction: + */ + ExtractGetOrPostRequest.Descriptor, - /* - * Verification request handling: - */ - EnablePassthroughMode.Descriptor, + /* + * Verification request handling: + */ + EnablePassthroughMode.Descriptor, - /* - * Verification response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - ProcessHostRedirectionResponse.Descriptor, - ProcessPassthroughErrorResponse.Descriptor, - ProcessLocalErrorResponse.Descriptor, - ProcessEmptyResponse.Descriptor); - } + /* + * Verification response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + ProcessHostRedirectionResponse.Descriptor, + ProcessPassthroughErrorResponse.Descriptor, + ProcessLocalErrorResponse.Descriptor, + ProcessEmptyResponse.Descriptor); + } + /// + /// Contains the logic responsible of processing verification responses that should trigger a host redirection. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessHostRedirectionResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing verification responses that should trigger a host redirection. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessHostRedirectionResponse : IOpenIddictServerHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessPassthroughErrorResponse.Descriptor.Order - 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessPassthroughErrorResponse.Descriptor.Order - 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); - /// - public ValueTask HandleAsync(ApplyVerificationResponseContext context) + /// + public ValueTask HandleAsync(ApplyVerificationResponseContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + throw new ArgumentNullException(nameof(context)); + } - // Note: this handler only redirects the user agent to the address specified in - // the properties when there's no error or if the error is an access_denied error. - if (!string.IsNullOrEmpty(context.Response.Error) && - !string.Equals(context.Response.Error, Errors.AccessDenied, StringComparison.Ordinal)) - { - return default; - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is not null && !string.IsNullOrEmpty(properties.RedirectUri)) - { - response.Redirect(properties.RedirectUri); + // Note: this handler only redirects the user agent to the address specified in + // the properties when there's no error or if the error is an access_denied error. + if (!string.IsNullOrEmpty(context.Response.Error) && + !string.Equals(context.Response.Error, Errors.AccessDenied, StringComparison.Ordinal)) + { + return default; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6144)); - context.HandleRequest(); - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is not null && !string.IsNullOrEmpty(properties.RedirectUri)) + { + response.Redirect(properties.RedirectUri); - return default; + context.Logger.LogInformation(SR.GetResourceString(SR.ID6144)); + context.HandleRequest(); } + + return default; } } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Discovery.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Discovery.cs index 4688a02b..7531f81b 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Discovery.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Discovery.cs @@ -7,36 +7,35 @@ using System.Collections.Immutable; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +public static partial class OpenIddictServerOwinHandlers { - public static partial class OpenIddictServerOwinHandlers + public static class Discovery { - public static class Discovery - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Configuration request extraction: - */ - ExtractGetRequest.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Configuration request extraction: + */ + ExtractGetRequest.Descriptor, - /* - * Configuration response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor, + /* + * Configuration response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor, - /* - * Cryptography request extraction: - */ - ExtractGetRequest.Descriptor, + /* + * Cryptography request extraction: + */ + ExtractGetRequest.Descriptor, - /* - * Cryptography response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Cryptography response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs index 2880598f..52fed871 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs @@ -8,31 +8,30 @@ using System.Collections.Immutable; using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +public static partial class OpenIddictServerOwinHandlers { - public static partial class OpenIddictServerOwinHandlers + public static class Exchange { - public static class Exchange - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Token request extraction: - */ - ExtractPostRequest.Descriptor, - ExtractBasicAuthenticationCredentials.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Token request extraction: + */ + ExtractPostRequest.Descriptor, + ExtractBasicAuthenticationCredentials.Descriptor, - /* - * Token request handling: - */ - EnablePassthroughMode.Descriptor, + /* + * Token request handling: + */ + EnablePassthroughMode.Descriptor, - /* - * Token response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Token response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs index 00765b1e..1696c2f8 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs @@ -7,25 +7,24 @@ using System.Collections.Immutable; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +public static partial class OpenIddictServerOwinHandlers { - public static partial class OpenIddictServerOwinHandlers + public static class Introspection { - public static class Introspection - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Introspection request extraction: - */ - ExtractGetOrPostRequest.Descriptor, - ExtractBasicAuthenticationCredentials.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Introspection request extraction: + */ + ExtractGetOrPostRequest.Descriptor, + ExtractBasicAuthenticationCredentials.Descriptor, - /* - * Introspection response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Introspection response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs index ffbbb8b8..c14f2c33 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs @@ -7,26 +7,25 @@ using System.Collections.Immutable; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +public static partial class OpenIddictServerOwinHandlers { - public static partial class OpenIddictServerOwinHandlers + public static class Revocation { - public static class Revocation - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Revocation request extraction: - */ - ExtractPostRequest.Descriptor, - ExtractBasicAuthenticationCredentials.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Revocation request extraction: + */ + ExtractPostRequest.Descriptor, + ExtractBasicAuthenticationCredentials.Descriptor, - /* - * Revocation response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Revocation response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs index df63c363..f5920653 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs @@ -28,393 +28,392 @@ using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters; using JsonWebTokenTypes = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.JsonWebTokenTypes; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +public static partial class OpenIddictServerOwinHandlers { - public static partial class OpenIddictServerOwinHandlers + public static class Session { - public static class Session + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Logout request extraction: + */ + ExtractGetOrPostRequest.Descriptor, + RestoreCachedRequestParameters.Descriptor, + CacheRequestParameters.Descriptor, + + /* + * Logout request handling: + */ + EnablePassthroughMode.Descriptor, + + /* + * Logout response processing: + */ + RemoveCachedRequest.Descriptor, + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + ProcessHostRedirectionResponse.Descriptor, + ProcessPassthroughErrorResponse.Descriptor, + ProcessLocalErrorResponse.Descriptor, + ProcessQueryResponse.Descriptor, + ProcessEmptyResponse.Descriptor); + + /// + /// Contains the logic responsible of restoring cached requests from the request_id, if specified. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class RestoreCachedRequestParameters : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Logout request extraction: - */ - ExtractGetOrPostRequest.Descriptor, - RestoreCachedRequestParameters.Descriptor, - CacheRequestParameters.Descriptor, - - /* - * Logout request handling: - */ - EnablePassthroughMode.Descriptor, - - /* - * Logout response processing: - */ - RemoveCachedRequest.Descriptor, - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - ProcessHostRedirectionResponse.Descriptor, - ProcessPassthroughErrorResponse.Descriptor, - ProcessLocalErrorResponse.Descriptor, - ProcessQueryResponse.Descriptor, - ProcessEmptyResponse.Descriptor); + private readonly IDistributedCache _cache; + + public RestoreCachedRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public RestoreCachedRequestParameters(IDistributedCache cache) + => _cache = cache; /// - /// Contains the logic responsible of restoring cached requests from the request_id, if specified. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class RestoreCachedRequestParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ExtractLogoutRequestContext context) { - private readonly IDistributedCache _cache; - - public RestoreCachedRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - - public RestoreCachedRequestParameters(IDistributedCache cache) - => _cache = cache; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ExtractLogoutRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); + Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - // If a request_id parameter can be found in the logout request, - // restore the complete logout request from the distributed cache. + // If a request_id parameter can be found in the logout request, + // restore the complete logout request from the distributed cache. - if (string.IsNullOrEmpty(context.Request.RequestId)) - { - return; - } + if (string.IsNullOrEmpty(context.Request.RequestId)) + { + return; + } - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - var token = await _cache.GetStringAsync(Cache.LogoutRequest + context.Request.RequestId); - if (token is null || !context.Options.JsonWebTokenHandler.CanReadToken(token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6150), Parameters.RequestId); + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + var token = await _cache.GetStringAsync(Cache.LogoutRequest + context.Request.RequestId); + if (token is null || !context.Options.JsonWebTokenHandler.CanReadToken(token)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6150), Parameters.RequestId); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.RequestId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.RequestId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } - var parameters = context.Options.TokenValidationParameters.Clone(); - parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; - parameters.ValidAudience = context.Issuer?.AbsoluteUri; - parameters.ValidTypes = new[] { JsonWebTokenTypes.Private.LogoutRequest }; + var parameters = context.Options.TokenValidationParameters.Clone(); + parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; + parameters.ValidAudience = context.Issuer?.AbsoluteUri; + parameters.ValidTypes = new[] { JsonWebTokenTypes.Private.LogoutRequest }; - var result = context.Options.JsonWebTokenHandler.ValidateToken(token, parameters); - if (!result.IsValid) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6150), Parameters.RequestId); + var result = context.Options.JsonWebTokenHandler.ValidateToken(token, parameters); + if (!result.IsValid) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6150), Parameters.RequestId); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.RequestId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.RequestId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } - using var document = JsonDocument.Parse( - Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload)); - if (document.RootElement.ValueKind != JsonValueKind.Object) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0118)); - } + using var document = JsonDocument.Parse( + Base64UrlEncoder.Decode(((JsonWebToken) result.SecurityToken).InnerToken.EncodedPayload)); + if (document.RootElement.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0118)); + } - // Restore the authorization request parameters from the serialized payload - foreach (var parameter in document.RootElement.EnumerateObject()) + // Restore the authorization request parameters from the serialized payload + foreach (var parameter in document.RootElement.EnumerateObject()) + { + // Avoid overriding the current request parameters. + if (context.Request.HasParameter(parameter.Name)) { - // Avoid overriding the current request parameters. - if (context.Request.HasParameter(parameter.Name)) - { - continue; - } - - context.Request.SetParameter(parameter.Name, parameter.Value.Clone()); + continue; } + + context.Request.SetParameter(parameter.Name, parameter.Value.Clone()); } } + } + + /// + /// Contains the logic responsible of caching logout requests, if applicable. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class CacheRequestParameters : IOpenIddictServerHandler + { + private readonly IDistributedCache _cache; + private readonly IOptionsMonitor _options; + + public CacheRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + + public CacheRequestParameters( + IDistributedCache cache, + IOptionsMonitor options) + { + _cache = cache; + _options = options; + } /// - /// Contains the logic responsible of caching logout requests, if applicable. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class CacheRequestParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(RestoreCachedRequestParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ExtractLogoutRequestContext context) { - private readonly IDistributedCache _cache; - private readonly IOptionsMonitor _options; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public CacheRequestParameters() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); + Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - public CacheRequestParameters( - IDistributedCache cache, - IOptionsMonitor options) + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) { - _cache = cache; - _options = options; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); } - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(RestoreCachedRequestParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ExtractLogoutRequestContext context) + // Don't cache the request if the request doesn't include any parameter. + // If a request_id parameter can be found in the logout request, + // ignore the following logic to prevent an infinite redirect loop. + if (context.Request.Count == 0 || !string.IsNullOrEmpty(context.Request.RequestId)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return; + } - Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); + // Generate a 256-bit request identifier using a crypto-secure random number generator. + var data = new byte[256 / 8]; + using var generator = RandomNumberGenerator.Create(); + generator.GetBytes(data); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + context.Request.RequestId = Base64UrlEncoder.Encode(data); - // Don't cache the request if the request doesn't include any parameter. - // If a request_id parameter can be found in the logout request, - // ignore the following logic to prevent an infinite redirect loop. - if (context.Request.Count == 0 || !string.IsNullOrEmpty(context.Request.RequestId)) - { - return; - } + // Store the serialized logout request parameters in the distributed cache. + var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor + { + Audience = context.Issuer?.AbsoluteUri, + Claims = context.Request.GetParameters().ToDictionary( + parameter => parameter.Key, + parameter => parameter.Value.Value), + EncryptingCredentials = context.Options.EncryptionCredentials.First(), + Issuer = context.Issuer?.AbsoluteUri, + SigningCredentials = context.Options.SigningCredentials.First(), + Subject = new ClaimsIdentity(), + TokenType = JsonWebTokenTypes.Private.LogoutRequest + }); + + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + await _cache.SetStringAsync(Cache.LogoutRequest + context.Request.RequestId, + token, _options.CurrentValue.LogoutRequestCachingPolicy); + + // Create a new GET logout request containing only the request_id parameter. + var address = WebUtilities.AddQueryString( + uri: request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase + request.Path, + name: Parameters.RequestId, + value: context.Request.RequestId); + + request.Context.Response.Redirect(address); + + // Mark the response as handled to skip the rest of the pipeline. + context.HandleRequest(); + } + } - // Generate a 256-bit request identifier using a crypto-secure random number generator. - var data = new byte[256 / 8]; - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); + /// + /// Contains the logic responsible of removing cached logout requests from the distributed cache. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class RemoveCachedRequest : IOpenIddictServerHandler + { + private readonly IDistributedCache _cache; - context.Request.RequestId = Base64UrlEncoder.Encode(data); + public RemoveCachedRequest() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - // Store the serialized logout request parameters in the distributed cache. - var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor - { - Audience = context.Issuer?.AbsoluteUri, - Claims = context.Request.GetParameters().ToDictionary( - parameter => parameter.Key, - parameter => parameter.Value.Value), - EncryptingCredentials = context.Options.EncryptionCredentials.First(), - Issuer = context.Issuer?.AbsoluteUri, - SigningCredentials = context.Options.SigningCredentials.First(), - Subject = new ClaimsIdentity(), - TokenType = JsonWebTokenTypes.Private.LogoutRequest - }); - - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - await _cache.SetStringAsync(Cache.LogoutRequest + context.Request.RequestId, - token, _options.CurrentValue.LogoutRequestCachingPolicy); - - // Create a new GET logout request containing only the request_id parameter. - var address = WebUtilities.AddQueryString( - uri: request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase + request.Path, - name: Parameters.RequestId, - value: context.Request.RequestId); - - request.Context.Response.Redirect(address); - - // Mark the response as handled to skip the rest of the pipeline. - context.HandleRequest(); - } - } + public RemoveCachedRequest(IDistributedCache cache) + => _cache = cache; /// - /// Contains the logic responsible of removing cached logout requests from the distributed cache. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class RemoveCachedRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyLogoutResponseContext context) { - private readonly IDistributedCache _cache; - - public RemoveCachedRequest() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0116)); - - public RemoveCachedRequest(IDistributedCache cache) - => _cache = cache; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyLogoutResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - if (string.IsNullOrEmpty(context.Request?.RequestId)) - { - return default; - } + if (string.IsNullOrEmpty(context.Request?.RequestId)) + { + return default; + } - // Note: the ApplyLogoutResponse event is called for both successful - // and errored logout responses but discrimination is not necessary here, - // as the logout request must be removed from the distributed cache in both cases. + // Note: the ApplyLogoutResponse event is called for both successful + // and errored logout responses but discrimination is not necessary here, + // as the logout request must be removed from the distributed cache in both cases. - // Note: the cache key is always prefixed with a specific marker - // to avoid collisions with the other types of cached payloads. - return new ValueTask(_cache.RemoveAsync(Cache.LogoutRequest + context.Request.RequestId)); - } + // Note: the cache key is always prefixed with a specific marker + // to avoid collisions with the other types of cached payloads. + return new ValueTask(_cache.RemoveAsync(Cache.LogoutRequest + context.Request.RequestId)); } + } + /// + /// Contains the logic responsible of processing logout responses. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessQueryResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing logout responses. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessQueryResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessLocalErrorResponse.Descriptor.Order + 250) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyLogoutResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessLocalErrorResponse.Descriptor.Order + 250) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyLogoutResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) - { - return default; - } + if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) + { + return default; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6151), context.PostLogoutRedirectUri, response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6151), context.PostLogoutRedirectUri, response); - var location = context.PostLogoutRedirectUri; + var location = context.PostLogoutRedirectUri; - // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters - // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. - // For consistency, multiple parameters with the same name are also supported by this endpoint. - foreach (var (key, value) in - from parameter in context.Response.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) - { - location = WebUtilities.AddQueryString(location, key, value); - } + // Note: while initially not allowed by the core OAuth 2.0 specification, multiple parameters + // with the same name are used by derived drafts like the OAuth 2.0 token exchange specification. + // For consistency, multiple parameters with the same name are also supported by this endpoint. + foreach (var (key, value) in + from parameter in context.Response.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select (parameter.Key, Value: value)) + { + location = WebUtilities.AddQueryString(location, key, value); + } - response.Redirect(location); - context.HandleRequest(); + response.Redirect(location); + context.HandleRequest(); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of processing logout responses that should trigger a host redirection. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessHostRedirectionResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing logout responses that should trigger a host redirection. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessHostRedirectionResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ProcessPassthroughErrorResponse.Descriptor.Order + 250) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyLogoutResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ProcessPassthroughErrorResponse.Descriptor.Order + 250) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyLogoutResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + throw new ArgumentNullException(nameof(context)); + } - // Note: this handler only executes if no post_logout_redirect_uri was specified - // and if the response doesn't correspond to an error, that must be handled locally. - if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri) || - !string.IsNullOrEmpty(context.Response.Error)) - { - return default; - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is not null && !string.IsNullOrEmpty(properties.RedirectUri)) - { - response.Redirect(properties.RedirectUri); + // Note: this handler only executes if no post_logout_redirect_uri was specified + // and if the response doesn't correspond to an error, that must be handled locally. + if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri) || + !string.IsNullOrEmpty(context.Response.Error)) + { + return default; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6144)); - context.HandleRequest(); - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is not null && !string.IsNullOrEmpty(properties.RedirectUri)) + { + response.Redirect(properties.RedirectUri); - return default; + context.Logger.LogInformation(SR.GetResourceString(SR.ID6144)); + context.HandleRequest(); } + + return default; } } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Userinfo.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Userinfo.cs index b4a7db78..1395bf92 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Userinfo.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Userinfo.cs @@ -8,31 +8,30 @@ using System.Collections.Immutable; using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +public static partial class OpenIddictServerOwinHandlers { - public static partial class OpenIddictServerOwinHandlers + public static class Userinfo { - public static class Userinfo - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Userinfo request extraction: - */ - ExtractGetOrPostRequest.Descriptor, - ExtractAccessToken.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Userinfo request extraction: + */ + ExtractGetOrPostRequest.Descriptor, + ExtractAccessToken.Descriptor, - /* - * Userinfo request handling: - */ - EnablePassthroughMode.Descriptor, + /* + * Userinfo request handling: + */ + EnablePassthroughMode.Descriptor, - /* - * Userinfo response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessChallengeErrorResponse.Descriptor, - ProcessJsonResponse.Descriptor); - } + /* + * Userinfo response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessChallengeErrorResponse.Descriptor, + ProcessJsonResponse.Descriptor); } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs index be0b731c..a0dd7e67 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs @@ -28,618 +28,601 @@ using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters; using Properties = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class OpenIddictServerOwinHandlers { - [EditorBrowsable(EditorBrowsableState.Never)] - public static partial class OpenIddictServerOwinHandlers + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Top-level request processing: + */ + InferEndpointType.Descriptor, + InferIssuerFromHost.Descriptor, + ValidateTransportSecurityRequirement.Descriptor, + + /* + * Challenge processing: + */ + ResolveHostChallengeParameters.Descriptor) + .AddRange(Authentication.DefaultHandlers) + .AddRange(Device.DefaultHandlers) + .AddRange(Discovery.DefaultHandlers) + .AddRange(Exchange.DefaultHandlers) + .AddRange(Introspection.DefaultHandlers) + .AddRange(Revocation.DefaultHandlers) + .AddRange(Session.DefaultHandlers) + .AddRange(Userinfo.DefaultHandlers); + + /// + /// Contains the logic responsible of inferring the endpoint type from the request address. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class InferEndpointType : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Top-level request processing: - */ - InferEndpointType.Descriptor, - InferIssuerFromHost.Descriptor, - ValidateTransportSecurityRequirement.Descriptor, - - /* - * Challenge processing: - */ - ResolveHostChallengeParameters.Descriptor) - .AddRange(Authentication.DefaultHandlers) - .AddRange(Device.DefaultHandlers) - .AddRange(Discovery.DefaultHandlers) - .AddRange(Exchange.DefaultHandlers) - .AddRange(Introspection.DefaultHandlers) - .AddRange(Revocation.DefaultHandlers) - .AddRange(Session.DefaultHandlers) - .AddRange(Userinfo.DefaultHandlers); - /// - /// Contains the logic responsible of inferring the endpoint type from the request address. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class InferEndpointType : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + // Note: this handler must be invoked before any other handler, + // including the built-in handlers defined in OpenIddict.Server. + .SetOrder(int.MinValue + 50_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - // Note: this handler must be invoked before any other handler, - // including the built-in handlers defined in OpenIddict.Server. - .SetOrder(int.MinValue + 50_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - context.EndpointType = - Matches(request, context.Options.AuthorizationEndpointUris) ? OpenIddictServerEndpointType.Authorization : - Matches(request, context.Options.ConfigurationEndpointUris) ? OpenIddictServerEndpointType.Configuration : - Matches(request, context.Options.CryptographyEndpointUris) ? OpenIddictServerEndpointType.Cryptography : - Matches(request, context.Options.DeviceEndpointUris) ? OpenIddictServerEndpointType.Device : - Matches(request, context.Options.IntrospectionEndpointUris) ? OpenIddictServerEndpointType.Introspection : - Matches(request, context.Options.LogoutEndpointUris) ? OpenIddictServerEndpointType.Logout : - Matches(request, context.Options.RevocationEndpointUris) ? OpenIddictServerEndpointType.Revocation : - Matches(request, context.Options.TokenEndpointUris) ? OpenIddictServerEndpointType.Token : - Matches(request, context.Options.UserinfoEndpointUris) ? OpenIddictServerEndpointType.Userinfo : - Matches(request, context.Options.VerificationEndpointUris) ? OpenIddictServerEndpointType.Verification : - OpenIddictServerEndpointType.Unknown; - - if (context.EndpointType != OpenIddictServerEndpointType.Unknown) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6053), context.EndpointType); - } + context.EndpointType = + Matches(request, context.Options.AuthorizationEndpointUris) ? OpenIddictServerEndpointType.Authorization : + Matches(request, context.Options.ConfigurationEndpointUris) ? OpenIddictServerEndpointType.Configuration : + Matches(request, context.Options.CryptographyEndpointUris) ? OpenIddictServerEndpointType.Cryptography : + Matches(request, context.Options.DeviceEndpointUris) ? OpenIddictServerEndpointType.Device : + Matches(request, context.Options.IntrospectionEndpointUris) ? OpenIddictServerEndpointType.Introspection : + Matches(request, context.Options.LogoutEndpointUris) ? OpenIddictServerEndpointType.Logout : + Matches(request, context.Options.RevocationEndpointUris) ? OpenIddictServerEndpointType.Revocation : + Matches(request, context.Options.TokenEndpointUris) ? OpenIddictServerEndpointType.Token : + Matches(request, context.Options.UserinfoEndpointUris) ? OpenIddictServerEndpointType.Userinfo : + Matches(request, context.Options.VerificationEndpointUris) ? OpenIddictServerEndpointType.Verification : + OpenIddictServerEndpointType.Unknown; + + if (context.EndpointType != OpenIddictServerEndpointType.Unknown) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6053), context.EndpointType); + } - return default; + return default; - static bool Matches(IOwinRequest request, IReadOnlyList addresses) + static bool Matches(IOwinRequest request, IReadOnlyList addresses) + { + for (var index = 0; index < addresses.Count; index++) { - for (var index = 0; index < addresses.Count; index++) + var address = addresses[index]; + if (address.IsAbsoluteUri) { - var address = addresses[index]; - if (address.IsAbsoluteUri) + // If the request host is not available (e.g because HTTP/1.0 was used), ignore absolute URLs. + if (string.IsNullOrEmpty(request.Host.Value)) { - // If the request host is not available (e.g because HTTP/1.0 was used), ignore absolute URLs. - if (string.IsNullOrEmpty(request.Host.Value)) - { - continue; - } - - // Create a Uri instance using the request scheme and raw host and compare the two base addresses. - if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host, UriKind.Absolute, out Uri? uri) || - !uri.IsWellFormedOriginalString() || uri.Port != address.Port || - !string.Equals(uri.Scheme, address.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(uri.Host, address.Host, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var path = PathString.FromUriComponent(address); - if (AreEquivalent(path, request.PathBase + request.Path)) - { - return true; - } + continue; } - else if (address.OriginalString.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + // Create a Uri instance using the request scheme and raw host and compare the two base addresses. + if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host, UriKind.Absolute, out Uri? uri) || + !uri.IsWellFormedOriginalString() || uri.Port != address.Port || + !string.Equals(uri.Scheme, address.Scheme, StringComparison.OrdinalIgnoreCase) || + !string.Equals(uri.Host, address.Host, StringComparison.OrdinalIgnoreCase)) { - var path = new PathString(address.OriginalString); - if (AreEquivalent(path, request.Path)) - { - return true; - } + continue; } - } - return false; + var path = PathString.FromUriComponent(address); + if (AreEquivalent(path, request.PathBase + request.Path)) + { + return true; + } + } - // ASP.NET MVC's routing system ignores trailing slashes when determining - // whether the request path matches a registered route, which is not the case - // with PathString, that treats /connect/token and /connect/token/ as different - // addresses. To mitigate this inconsistency, a manual check is used here. - static bool AreEquivalent(PathString left, PathString right) - => left.Equals(right, StringComparison.OrdinalIgnoreCase) || - left.Equals(right + new PathString("/"), StringComparison.OrdinalIgnoreCase) || - right.Equals(left + new PathString("/"), StringComparison.OrdinalIgnoreCase); + else if (address.OriginalString.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + { + var path = new PathString(address.OriginalString); + if (AreEquivalent(path, request.Path)) + { + return true; + } + } } + + return false; + + // ASP.NET MVC's routing system ignores trailing slashes when determining + // whether the request path matches a registered route, which is not the case + // with PathString, that treats /connect/token and /connect/token/ as different + // addresses. To mitigate this inconsistency, a manual check is used here. + static bool AreEquivalent(PathString left, PathString right) + => left.Equals(right, StringComparison.OrdinalIgnoreCase) || + left.Equals(right + new PathString("/"), StringComparison.OrdinalIgnoreCase) || + right.Equals(left + new PathString("/"), StringComparison.OrdinalIgnoreCase); } } + } + /// + /// Contains the logic responsible of infering the issuer URL from the HTTP request host and validating it. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class InferIssuerFromHost : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of infering the issuer URL from the HTTP request host and validating it. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class InferIssuerFromHost : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(InferEndpointType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(InferEndpointType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } - - // Don't require that the request host be present if the request is not handled - // by an OpenIddict endpoint or if an explicit issuer URL was already set. - if (context.Issuer is not null || context.EndpointType == OpenIddictServerEndpointType.Unknown) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (string.IsNullOrEmpty(request.Host.Value)) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2081(Headers.Host), - uri: SR.FormatID8000(SR.ID2081)); + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - return default; - } + // Don't require that the request host be present if the request is not handled + // by an OpenIddict endpoint or if an explicit issuer URL was already set. + if (context.Issuer is not null || context.EndpointType == OpenIddictServerEndpointType.Unknown) + { + return default; + } - if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase, UriKind.Absolute, out Uri? issuer) || - !issuer.IsWellFormedOriginalString()) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2082(Headers.Host), - uri: SR.FormatID8000(SR.ID2082)); + if (string.IsNullOrEmpty(request.Host.Value)) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2081(Headers.Host), + uri: SR.FormatID8000(SR.ID2081)); - return default; - } + return default; + } - context.Issuer = issuer; + if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase, UriKind.Absolute, out Uri? issuer) || + !issuer.IsWellFormedOriginalString()) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2082(Headers.Host), + uri: SR.FormatID8000(SR.ID2082)); return default; } + + context.Issuer = issuer; + + return default; } + } + /// + /// Contains the logic responsible of rejecting OpenID Connect requests that don't use transport security. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ValidateTransportSecurityRequirement : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting OpenID Connect requests that don't use transport security. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTransportSecurityRequirement : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(InferEndpointType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(InferEndpointType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Don't require that the host be present if the request is not handled by OpenIddict. - if (context.EndpointType == OpenIddictServerEndpointType.Unknown) - { - return default; - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - // Reject authorization requests sent without transport security. - if (!request.IsSecure) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2083), - uri: SR.FormatID8000(SR.ID2083)); + // Don't require that the host be present if the request is not handled by OpenIddict. + if (context.EndpointType == OpenIddictServerEndpointType.Unknown) + { + return default; + } - return default; - } + // Reject authorization requests sent without transport security. + if (!request.IsSecure) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2083), + uri: SR.FormatID8000(SR.ID2083)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of resolving the additional sign-in parameters stored in the OWIN + /// authentication properties specified by the application that triggered the sign-in operation. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ResolveHostChallengeParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of resolving the additional sign-in parameters stored in the OWIN - /// authentication properties specified by the application that triggered the sign-in operation. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ResolveHostChallengeParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(AttachChallengeParameters.Descriptor.Order - 500) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachChallengeParameters.Descriptor.Order - 500) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessChallengeContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (properties.Dictionary.TryGetValue(Properties.Error, out string? error) && - !string.IsNullOrEmpty(error)) - { - context.Parameters[Parameters.Error] = error; - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is null) + { + return default; + } - if (properties.Dictionary.TryGetValue(Properties.ErrorDescription, out string? description) && - !string.IsNullOrEmpty(description)) - { - context.Parameters[Parameters.ErrorDescription] = description; - } + if (properties.Dictionary.TryGetValue(Properties.Error, out string? error) && + !string.IsNullOrEmpty(error)) + { + context.Parameters[Parameters.Error] = error; + } - if (properties.Dictionary.TryGetValue(Properties.ErrorUri, out string? uri) && - !string.IsNullOrEmpty(uri)) - { - context.Parameters[Parameters.ErrorUri] = uri; - } + if (properties.Dictionary.TryGetValue(Properties.ErrorDescription, out string? description) && + !string.IsNullOrEmpty(description)) + { + context.Parameters[Parameters.ErrorDescription] = description; + } - if (properties.Dictionary.TryGetValue(Properties.Scope, out string? scope) && - !string.IsNullOrEmpty(scope)) - { - context.Parameters[Parameters.Scope] = scope; - } + if (properties.Dictionary.TryGetValue(Properties.ErrorUri, out string? uri) && + !string.IsNullOrEmpty(uri)) + { + context.Parameters[Parameters.ErrorUri] = uri; + } - return default; + if (properties.Dictionary.TryGetValue(Properties.Scope, out string? scope) && + !string.IsNullOrEmpty(scope)) + { + context.Parameters[Parameters.Scope] = scope; } + + return default; } + } + /// + /// Contains the logic responsible of extracting OpenID Connect requests from GET HTTP requests. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ExtractGetRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting OpenID Connect requests from GET HTTP requests. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractGetRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ValidateTransportSecurityRequirement.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ValidateTransportSecurityRequirement.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (string.Equals(request.Method, "GET", StringComparison.OrdinalIgnoreCase)) - { - context.Transaction.Request = new OpenIddictRequest(request.Query); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - else - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + if (string.Equals(request.Method, "GET", StringComparison.OrdinalIgnoreCase)) + { + context.Transaction.Request = new OpenIddictRequest(request.Query); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2084), - uri: SR.FormatID8000(SR.ID2084)); + else + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2084), + uri: SR.FormatID8000(SR.ID2084)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of extracting OpenID Connect requests from GET or POST HTTP requests. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ExtractGetOrPostRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting OpenID Connect requests from GET or POST HTTP requests. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractGetOrPostRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ExtractGetRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ExtractGetRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - if (string.Equals(request.Method, "GET", StringComparison.OrdinalIgnoreCase)) - { - context.Transaction.Request = new OpenIddictRequest(request.Query); - } + if (string.Equals(request.Method, "GET", StringComparison.OrdinalIgnoreCase)) + { + context.Transaction.Request = new OpenIddictRequest(request.Query); + } - else if (string.Equals(request.Method, "POST", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(request.Method, "POST", StringComparison.OrdinalIgnoreCase)) + { + // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization + if (string.IsNullOrEmpty(request.ContentType)) { - // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization - if (string.IsNullOrEmpty(request.ContentType)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6138), Headers.ContentType); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2081(Headers.ContentType), - uri: SR.FormatID8000(SR.ID2081)); - - return; - } - - // May have media/type; charset=utf-8, allow partial match. - if (!request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6139), Headers.ContentType, request.ContentType); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6138), Headers.ContentType); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2082(Headers.ContentType), - uri: SR.FormatID8000(SR.ID2082)); - - return; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2081(Headers.ContentType), + uri: SR.FormatID8000(SR.ID2081)); - context.Transaction.Request = new OpenIddictRequest(await request.ReadFormAsync()); + return; } - else + // May have media/type; charset=utf-8, allow partial match. + if (!request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6139), Headers.ContentType, request.ContentType); context.Reject( error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2084), - uri: SR.FormatID8000(SR.ID2084)); + description: SR.FormatID2082(Headers.ContentType), + uri: SR.FormatID8000(SR.ID2082)); return; } + + context.Transaction.Request = new OpenIddictRequest(await request.ReadFormAsync()); + } + + else + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2084), + uri: SR.FormatID8000(SR.ID2084)); + + return; } } + } + /// + /// Contains the logic responsible of extracting OpenID Connect requests from POST HTTP requests. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ExtractPostRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting OpenID Connect requests from POST HTTP requests. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractPostRequest : IOpenIddictServerHandler where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ExtractGetOrPostRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - if (string.Equals(request.Method, "POST", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(request.Method, "POST", StringComparison.OrdinalIgnoreCase)) + { + // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization + if (string.IsNullOrEmpty(request.ContentType)) { - // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization - if (string.IsNullOrEmpty(request.ContentType)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6138), Headers.ContentType); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2081(Headers.ContentType), - uri: SR.FormatID8000(SR.ID2081)); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6138), Headers.ContentType); - return; - } - - // May have media/type; charset=utf-8, allow partial match. - if (!request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6139), Headers.ContentType, request.ContentType); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2082(Headers.ContentType), - uri: SR.FormatID8000(SR.ID2082)); - - return; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2081(Headers.ContentType), + uri: SR.FormatID8000(SR.ID2081)); - context.Transaction.Request = new OpenIddictRequest(await request.ReadFormAsync()); + return; } - else + // May have media/type; charset=utf-8, allow partial match. + if (!request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6139), Headers.ContentType, request.ContentType); context.Reject( error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2084), - uri: SR.FormatID8000(SR.ID2084)); + description: SR.FormatID2082(Headers.ContentType), + uri: SR.FormatID8000(SR.ID2082)); return; } + + context.Transaction.Request = new OpenIddictRequest(await request.ReadFormAsync()); + } + + else + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.Method); + + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2084), + uri: SR.FormatID8000(SR.ID2084)); + + return; } } + } + /// + /// Contains the logic responsible of extracting client credentials from the standard HTTP Authorization header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ExtractBasicAuthenticationCredentials : IOpenIddictServerHandler + where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting client credentials from the standard HTTP Authorization header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractBasicAuthenticationCredentials : IOpenIddictServerHandler - where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ExtractPostRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ExtractPostRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } - - var header = request.Headers[Headers.Authorization]; - if (string.IsNullOrEmpty(header) || !header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase)) - { - return default; - } - - // At this point, reject requests that use multiple client authentication methods. - // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information. - if (!string.IsNullOrEmpty(context.Transaction.Request.ClientAssertion) || - !string.IsNullOrEmpty(context.Transaction.Request.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6140)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2087), - uri: SR.FormatID8000(SR.ID2087)); + Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); - return default; - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - try - { - var value = header.Substring("Basic ".Length).Trim(); - var data = Encoding.ASCII.GetString(Convert.FromBase64String(value)); + var header = request.Headers[Headers.Authorization]; + if (string.IsNullOrEmpty(header) || !header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase)) + { + return default; + } - var index = data.IndexOf(':'); - if (index < 0) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2055), - uri: SR.FormatID8000(SR.ID2055)); + // At this point, reject requests that use multiple client authentication methods. + // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information. + if (!string.IsNullOrEmpty(context.Transaction.Request.ClientAssertion) || + !string.IsNullOrEmpty(context.Transaction.Request.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6140)); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2087), + uri: SR.FormatID8000(SR.ID2087)); - // Attach the basic authentication credentials to the request message. - context.Transaction.Request.ClientId = UnescapeDataString(data.Substring(0, index)); - context.Transaction.Request.ClientSecret = UnescapeDataString(data.Substring(index + 1)); + return default; + } - return default; - } + try + { + var value = header.Substring("Basic ".Length).Trim(); + var data = Encoding.ASCII.GetString(Convert.FromBase64String(value)); - catch + var index = data.IndexOf(':'); + if (index < 0) { context.Reject( error: Errors.InvalidRequest, @@ -649,584 +632,600 @@ namespace OpenIddict.Server.Owin return default; } - static string? UnescapeDataString(string data) - { - if (string.IsNullOrEmpty(data)) - { - return null; - } + // Attach the basic authentication credentials to the request message. + context.Transaction.Request.ClientId = UnescapeDataString(data.Substring(0, index)); + context.Transaction.Request.ClientSecret = UnescapeDataString(data.Substring(index + 1)); - return Uri.UnescapeDataString(data.Replace("+", "%20")); + return default; + } + + catch + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2055), + uri: SR.FormatID8000(SR.ID2055)); + + return default; + } + + static string? UnescapeDataString(string data) + { + if (string.IsNullOrEmpty(data)) + { + return null; } + + return Uri.UnescapeDataString(data.Replace("+", "%20")); } } + } + /// + /// Contains the logic responsible of extracting an access token from the standard HTTP Authorization header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ExtractAccessToken : IOpenIddictServerHandler + where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting an access token from the standard HTTP Authorization header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractAccessToken : IOpenIddictServerHandler - where TContext : BaseValidatingContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ExtractBasicAuthenticationCredentials.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ExtractBasicAuthenticationCredentials.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var header = request.Headers[Headers.Authorization]; - if (string.IsNullOrEmpty(header) || !header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) - { - return default; - } + Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); - // Attach the access token to the request message. - context.Transaction.Request.AccessToken = header.Substring("Bearer ".Length); + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } + var header = request.Headers[Headers.Authorization]; + if (string.IsNullOrEmpty(header) || !header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { return default; } + + // Attach the access token to the request message. + context.Transaction.Request.AccessToken = header.Substring("Bearer ".Length); + + return default; } + } + /// + /// Contains the logic responsible of enabling the pass-through mode for the received request. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class EnablePassthroughMode : IOpenIddictServerHandler + where TContext : BaseRequestContext + where TFilter : IOpenIddictServerHandlerFilter + { /// - /// Contains the logic responsible of enabling the pass-through mode for the received request. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class EnablePassthroughMode : IOpenIddictServerHandler - where TContext : BaseRequestContext - where TFilter : IOpenIddictServerHandlerFilter + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.SkipRequest(); + context.SkipRequest(); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching an appropriate HTTP status code. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class AttachHttpResponseCode : IOpenIddictServerHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of attaching an appropriate HTTP status code. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachHttpResponseCode : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - // When client authentication is made using basic authentication, the authorization server MUST return - // a 401 response with a valid WWW-Authenticate header containing the Basic scheme and a non-empty realm. - // A similar error MAY be returned even when basic authentication is not used and MUST also be returned - // when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme. - // To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors - // and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials - // were specified in the request form instead of the HTTP headers, as allowed by the specification. - response.StatusCode = context.Transaction.Response.Error switch - { - null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects). + // When client authentication is made using basic authentication, the authorization server MUST return + // a 401 response with a valid WWW-Authenticate header containing the Basic scheme and a non-empty realm. + // A similar error MAY be returned even when basic authentication is not used and MUST also be returned + // when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme. + // To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors + // and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials + // were specified in the request form instead of the HTTP headers, as allowed by the specification. + response.StatusCode = context.Transaction.Response.Error switch + { + null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects). - Errors.InvalidClient or Errors.InvalidToken or Errors.MissingToken => 401, + Errors.InvalidClient or Errors.InvalidToken or Errors.MissingToken => 401, - Errors.InsufficientAccess or Errors.InsufficientScope => 403, + Errors.InsufficientAccess or Errors.InsufficientScope => 403, - _ => 400 - }; + _ => 400 + }; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the appropriate HTTP response cache headers. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class AttachCacheControlHeader : IOpenIddictServerHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of attaching the appropriate HTTP response cache headers. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachCacheControlHeader : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachHttpResponseCode.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachHttpResponseCode.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - // Prevent the response from being cached. - response.Headers[Headers.CacheControl] = "no-store"; - response.Headers[Headers.Pragma] = "no-cache"; - response.Headers[Headers.Expires] = "Thu, 01 Jan 1970 00:00:00 GMT"; + // Prevent the response from being cached. + response.Headers[Headers.CacheControl] = "no-store"; + response.Headers[Headers.Pragma] = "no-cache"; + response.Headers[Headers.Expires] = "Thu, 01 Jan 1970 00:00:00 GMT"; - return default; - } + return default; } + } + + /// + /// Contains the logic responsible of attaching errors details to the WWW-Authenticate header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class AttachWwwAuthenticateHeader : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOptionsMonitor _options; + + public AttachWwwAuthenticateHeader(IOptionsMonitor options) + => _options = options; /// - /// Contains the logic responsible of attaching errors details to the WWW-Authenticate header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachWwwAuthenticateHeader : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachCacheControlHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - private readonly IOptionsMonitor _options; - - public AttachWwwAuthenticateHeader(IOptionsMonitor options) - => _options = options; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachCacheControlHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - // When client authentication is made using basic authentication, the authorization server MUST return - // a 401 response with a valid WWW-Authenticate header containing the HTTP Basic authentication scheme. - // A similar error MAY be returned even when basic authentication is not used and MUST also be returned - // when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme. - // To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors - // and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials - // were specified in the request form instead of the HTTP headers, as allowed by the specification. - var scheme = context.Transaction.Response.Error switch - { - Errors.InvalidClient => Schemes.Basic, + // When client authentication is made using basic authentication, the authorization server MUST return + // a 401 response with a valid WWW-Authenticate header containing the HTTP Basic authentication scheme. + // A similar error MAY be returned even when basic authentication is not used and MUST also be returned + // when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme. + // To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors + // and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials + // were specified in the request form instead of the HTTP headers, as allowed by the specification. + var scheme = context.Transaction.Response.Error switch + { + Errors.InvalidClient => Schemes.Basic, - Errors.InvalidToken or - Errors.MissingToken or - Errors.InsufficientAccess or - Errors.InsufficientScope => Schemes.Bearer, + Errors.InvalidToken or + Errors.MissingToken or + Errors.InsufficientAccess or + Errors.InsufficientScope => Schemes.Bearer, - _ => null - }; + _ => null + }; - if (string.IsNullOrEmpty(scheme)) - { - return default; - } + if (string.IsNullOrEmpty(scheme)) + { + return default; + } - var parameters = new Dictionary(StringComparer.Ordinal); + var parameters = new Dictionary(StringComparer.Ordinal); - // If a realm was configured in the options, attach it to the parameters. - if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) - { - parameters[Parameters.Realm] = _options.CurrentValue.Realm; - } + // If a realm was configured in the options, attach it to the parameters. + if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) + { + parameters[Parameters.Realm] = _options.CurrentValue.Realm; + } - foreach (var parameter in context.Transaction.Response.GetParameters()) + foreach (var parameter in context.Transaction.Response.GetParameters()) + { + // Note: the error details are only included if the error was not caused by a missing token, as recommended + // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. + if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && + (string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || + string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || + string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) { - // Note: the error details are only included if the error was not caused by a missing token, as recommended - // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. - if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && - (string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || - string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || - string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) - { - continue; - } - - // Ignore values that can't be represented as unique strings. - var value = (string?) parameter.Value; - if (string.IsNullOrEmpty(value)) - { - continue; - } - - parameters[parameter.Key] = value; + continue; } - var builder = new StringBuilder(scheme); - - foreach (var parameter in parameters) + // Ignore values that can't be represented as unique strings. + var value = (string?) parameter.Value; + if (string.IsNullOrEmpty(value)) { - builder.Append(' '); - builder.Append(parameter.Key); - builder.Append('='); - builder.Append('"'); - builder.Append(parameter.Value.Replace("\"", "\\\"")); - builder.Append('"'); - builder.Append(','); + continue; } - // If the WWW-Authenticate header ends with a comma, remove it. - if (builder[builder.Length - 1] == ',') - { - builder.Remove(builder.Length - 1, 1); - } + parameters[parameter.Key] = value; + } - response.Headers.Append(Headers.WwwAuthenticate, builder.ToString()); + var builder = new StringBuilder(scheme); - return default; + foreach (var parameter in parameters) + { + builder.Append(' '); + builder.Append(parameter.Key); + builder.Append('='); + builder.Append('"'); + builder.Append(parameter.Value.Replace("\"", "\\\"")); + builder.Append('"'); + builder.Append(','); } + + // If the WWW-Authenticate header ends with a comma, remove it. + if (builder[builder.Length - 1] == ',') + { + builder.Remove(builder.Length - 1, 1); + } + + response.Headers.Append(Headers.WwwAuthenticate, builder.ToString()); + + return default; } + } + /// + /// Contains the logic responsible of processing challenge responses that contain a WWW-Authenticate header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessChallengeErrorResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing challenge responses that contain a WWW-Authenticate header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessChallengeErrorResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachWwwAuthenticateHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachWwwAuthenticateHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } - - // If the response doesn't contain a WWW-Authenticate header, don't return an empty response. - if (!response.Headers.ContainsKey(Headers.WwwAuthenticate)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6141), context.Transaction.Response); - context.HandleRequest(); + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } + // If the response doesn't contain a WWW-Authenticate header, don't return an empty response. + if (!response.Headers.ContainsKey(Headers.WwwAuthenticate)) + { return default; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6141), context.Transaction.Response); + context.HandleRequest(); + + return default; } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessJsonResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessJsonResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ProcessChallengeErrorResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ProcessChallengeErrorResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response); - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = true - }); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = true + }); - context.Transaction.Response.WriteTo(writer); - writer.Flush(); + context.Transaction.Response.WriteTo(writer); + writer.Flush(); - response.ContentLength = stream.Length; - response.ContentType = "application/json;charset=UTF-8"; + response.ContentLength = stream.Length; + response.ContentType = "application/json;charset=UTF-8"; - stream.Seek(offset: 0, loc: SeekOrigin.Begin); - await stream.CopyToAsync(response.Body, 4096, response.Context.Request.CallCancelled); + stream.Seek(offset: 0, loc: SeekOrigin.Begin); + await stream.CopyToAsync(response.Body, 4096, response.Context.Request.CallCancelled); - context.HandleRequest(); - } + context.HandleRequest(); } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses that must be handled by another + /// middleware in the pipeline at a later stage (e.g an ASP.NET MVC action or a NancyFX module). + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessPassthroughErrorResponse : IOpenIddictServerHandler + where TContext : BaseRequestContext + where TFilter : IOpenIddictServerHandlerFilter + { /// - /// Contains the logic responsible of processing OpenID Connect responses that must be handled by another - /// middleware in the pipeline at a later stage (e.g an ASP.NET MVC action or a NancyFX module). - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessPassthroughErrorResponse : IOpenIddictServerHandler - where TContext : BaseRequestContext - where TFilter : IOpenIddictServerHandlerFilter + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ProcessJsonResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ProcessJsonResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - if (string.IsNullOrEmpty(context.Transaction.Response.Error)) - { - return default; - } + if (string.IsNullOrEmpty(context.Transaction.Response.Error)) + { + return default; + } - // Don't return the state originally sent by the client application. - context.Transaction.Response.State = null; + // Don't return the state originally sent by the client application. + context.Transaction.Response.State = null; - context.SkipRequest(); + context.SkipRequest(); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses that must be returned as plain-text. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessLocalErrorResponse : IOpenIddictServerHandler + where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing OpenID Connect responses that must be returned as plain-text. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessLocalErrorResponse : IOpenIddictServerHandler - where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ProcessPassthroughErrorResponse>.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ProcessPassthroughErrorResponse>.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - if (string.IsNullOrEmpty(context.Transaction.Response.Error)) - { - return; - } + if (string.IsNullOrEmpty(context.Transaction.Response.Error)) + { + return; + } - // Don't return the state originally sent by the client application. - context.Transaction.Response.State = null; + // Don't return the state originally sent by the client application. + context.Transaction.Response.State = null; - context.Logger.LogInformation(SR.GetResourceString(SR.ID6143), context.Transaction.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6143), context.Transaction.Response); - using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream); + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); - foreach (var parameter in context.Transaction.Response.GetParameters()) + foreach (var parameter in context.Transaction.Response.GetParameters()) + { + // Ignore null or empty parameters, including JSON + // objects that can't be represented as strings. + var value = (string?) parameter.Value; + if (string.IsNullOrEmpty(value)) { - // Ignore null or empty parameters, including JSON - // objects that can't be represented as strings. - var value = (string?) parameter.Value; - if (string.IsNullOrEmpty(value)) - { - continue; - } - - writer.Write(parameter.Key); - writer.Write(':'); - writer.Write(value); - writer.WriteLine(); + continue; } - writer.Flush(); + writer.Write(parameter.Key); + writer.Write(':'); + writer.Write(value); + writer.WriteLine(); + } - response.ContentLength = stream.Length; - response.ContentType = "text/plain;charset=UTF-8"; + writer.Flush(); - stream.Seek(offset: 0, loc: SeekOrigin.Begin); - await stream.CopyToAsync(response.Body, 4096, response.Context.Request.CallCancelled); + response.ContentLength = stream.Length; + response.ContentType = "text/plain;charset=UTF-8"; - context.HandleRequest(); - } + stream.Seek(offset: 0, loc: SeekOrigin.Begin); + await stream.CopyToAsync(response.Body, 4096, response.Context.Request.CallCancelled); + + context.HandleRequest(); } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses that don't specify any parameter. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessEmptyResponse : IOpenIddictServerHandler + where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing OpenID Connect responses that don't specify any parameter. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessEmptyResponse : IOpenIddictServerHandler - where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6145)); - context.HandleRequest(); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6145)); + context.HandleRequest(); - return default; - } + return default; } } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHelpers.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHelpers.cs index 18fb31ad..d08e13db 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHelpers.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHelpers.cs @@ -11,98 +11,97 @@ using OpenIddict.Server; using OpenIddict.Server.Owin; using static OpenIddict.Server.OpenIddictServerEvents; -namespace Owin +namespace Owin; + +/// +/// Exposes companion extensions for the OpenIddict/OWIN integration. +/// +public static class OpenIddictServerOwinHelpers { /// - /// Exposes companion extensions for the OpenIddict/OWIN integration. + /// Registers the OpenIddict server OWIN middleware in the application pipeline. + /// Note: when using a dependency injection container supporting per-request + /// middleware resolution (like Autofac), calling this method is NOT recommended. /// - public static class OpenIddictServerOwinHelpers + /// The application builder used to register middleware instances. + /// The . + public static IAppBuilder UseOpenIddictServer(this IAppBuilder app) { - /// - /// Registers the OpenIddict server OWIN middleware in the application pipeline. - /// Note: when using a dependency injection container supporting per-request - /// middleware resolution (like Autofac), calling this method is NOT recommended. - /// - /// The application builder used to register middleware instances. - /// The . - public static IAppBuilder UseOpenIddictServer(this IAppBuilder app) + if (app is null) { - if (app is null) - { - throw new ArgumentNullException(nameof(app)); - } - - return app.Use(); + throw new ArgumentNullException(nameof(app)); } - /// - /// Retrieves the instance stored in the properties. - /// - /// The transaction instance. - /// The instance or null if it couldn't be found. - public static IOwinRequest? GetOwinRequest(this OpenIddictServerTransaction transaction) - { - if (transaction is null) - { - throw new ArgumentNullException(nameof(transaction)); - } - - if (!transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)) - { - return null; - } + return app.Use(); + } - if (property is WeakReference reference && reference.TryGetTarget(out IOwinRequest? request)) - { - return request; - } + /// + /// Retrieves the instance stored in the properties. + /// + /// The transaction instance. + /// The instance or null if it couldn't be found. + public static IOwinRequest? GetOwinRequest(this OpenIddictServerTransaction transaction) + { + if (transaction is null) + { + throw new ArgumentNullException(nameof(transaction)); + } + if (!transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)) + { return null; } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The . - public static OpenIddictServerEndpointType GetOpenIddictServerEndpointType(this IOwinContext context) + if (property is WeakReference reference && reference.TryGetTarget(out IOwinRequest? request)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return context.Get(typeof(OpenIddictServerTransaction).FullName)?.EndpointType ?? default; + return request; } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The instance or null if it couldn't be found. - public static OpenIddictRequest? GetOpenIddictServerRequest(this IOwinContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return null; + } - return context.Get(typeof(OpenIddictServerTransaction).FullName)?.Request; + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The . + public static OpenIddictServerEndpointType GetOpenIddictServerEndpointType(this IOwinContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The instance or null if it couldn't be found. - public static OpenIddictResponse? GetOpenIddictServerResponse(this IOwinContext context) + return context.Get(typeof(OpenIddictServerTransaction).FullName)?.EndpointType ?? default; + } + + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The instance or null if it couldn't be found. + public static OpenIddictRequest? GetOpenIddictServerRequest(this IOwinContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - return context.Get(typeof(OpenIddictServerTransaction).FullName)?.Response; + return context.Get(typeof(OpenIddictServerTransaction).FullName)?.Request; + } + + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The instance or null if it couldn't be found. + public static OpenIddictResponse? GetOpenIddictServerResponse(this IOwinContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); } + + return context.Get(typeof(OpenIddictServerTransaction).FullName)?.Response; } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddleware.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddleware.cs index c05f1ea3..3123806e 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddleware.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddleware.cs @@ -8,42 +8,41 @@ using Microsoft.Extensions.Options; using Microsoft.Owin; using Microsoft.Owin.Security.Infrastructure; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +/// +/// Provides the entry point necessary to register the OpenIddict server handler in an OWIN pipeline. +/// Note: this middleware is intented to be used with dependency injection containers +/// that support middleware resolution, like Autofac. Since it depends on scoped services, +/// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware. +/// +public class OpenIddictServerOwinMiddleware : AuthenticationMiddleware { + private readonly IOpenIddictServerDispatcher _dispatcher; + private readonly IOpenIddictServerFactory _factory; + /// - /// Provides the entry point necessary to register the OpenIddict server handler in an OWIN pipeline. - /// Note: this middleware is intented to be used with dependency injection containers - /// that support middleware resolution, like Autofac. Since it depends on scoped services, - /// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware. + /// Creates a new instance of the class. /// - public class OpenIddictServerOwinMiddleware : AuthenticationMiddleware + /// The next middleware in the pipeline, if applicable. + /// The OpenIddict server OWIN options. + /// The OpenIddict server dispatcher. + /// The OpenIddict server factory. + public OpenIddictServerOwinMiddleware( + OwinMiddleware? next, + IOptionsMonitor options, + IOpenIddictServerDispatcher dispatcher, + IOpenIddictServerFactory factory) + : base(next, options.CurrentValue) { - private readonly IOpenIddictServerDispatcher _dispatcher; - private readonly IOpenIddictServerFactory _factory; - - /// - /// Creates a new instance of the class. - /// - /// The next middleware in the pipeline, if applicable. - /// The OpenIddict server OWIN options. - /// The OpenIddict server dispatcher. - /// The OpenIddict server factory. - public OpenIddictServerOwinMiddleware( - OwinMiddleware? next, - IOptionsMonitor options, - IOpenIddictServerDispatcher dispatcher, - IOpenIddictServerFactory factory) - : base(next, options.CurrentValue) - { - _dispatcher = dispatcher; - _factory = factory; - } - - /// - /// Creates and returns a new instance. - /// - /// A new instance of the class. - protected override AuthenticationHandler CreateHandler() - => new OpenIddictServerOwinHandler(_dispatcher, _factory); + _dispatcher = dispatcher; + _factory = factory; } + + /// + /// Creates and returns a new instance. + /// + /// A new instance of the class. + protected override AuthenticationHandler CreateHandler() + => new OpenIddictServerOwinHandler(_dispatcher, _factory); } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddlewareFactory.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddlewareFactory.cs index c23edf6f..9f884856 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddlewareFactory.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinMiddlewareFactory.cs @@ -11,59 +11,58 @@ using Microsoft.Extensions.Options; using Microsoft.Owin; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +/// +/// Provides the entry point necessary to instantiate and register the scoped +/// in an OWIN/Katana pipeline. +/// +public class OpenIddictServerOwinMiddlewareFactory : OwinMiddleware { /// - /// Provides the entry point necessary to instantiate and register the scoped - /// in an OWIN/Katana pipeline. + /// Creates a new instance of the class. /// - public class OpenIddictServerOwinMiddlewareFactory : OwinMiddleware + /// The next middleware in the pipeline, if applicable. + public OpenIddictServerOwinMiddlewareFactory(OwinMiddleware? next) + : base(next) { - /// - /// Creates a new instance of the class. - /// - /// The next middleware in the pipeline, if applicable. - public OpenIddictServerOwinMiddlewareFactory(OwinMiddleware? next) - : base(next) + } + + /// + /// Resolves the instance from the OWIN context + /// and creates a new instance of the class, + /// which is used to register in the pipeline. + /// + /// The . + /// + /// A that can be used to monitor the asynchronous operation. + /// + public override Task Invoke(IOwinContext context) + { + if (context is null) { + throw new ArgumentNullException(nameof(context)); } - /// - /// Resolves the instance from the OWIN context - /// and creates a new instance of the class, - /// which is used to register in the pipeline. - /// - /// The . - /// - /// A that can be used to monitor the asynchronous operation. - /// - public override Task Invoke(IOwinContext context) + var provider = context.Get(typeof(IServiceProvider).FullName); + if (provider is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var provider = context.Get(typeof(IServiceProvider).FullName); - if (provider is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0121)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0121)); + } - // Note: the Microsoft.Extensions.DependencyInjection container doesn't support resolving services - // with arbitrary parameters, which prevents the server OWIN middleware from being resolved directly - // from the DI container, as the next middleware in the pipeline cannot be specified as a parameter. - // To work around this limitation, the server OWIN middleware is manually instantiated and invoked. - var middleware = new OpenIddictServerOwinMiddleware( - next: Next, - options: GetRequiredService>(provider), - dispatcher: GetRequiredService(provider), - factory: GetRequiredService(provider)); + // Note: the Microsoft.Extensions.DependencyInjection container doesn't support resolving services + // with arbitrary parameters, which prevents the server OWIN middleware from being resolved directly + // from the DI container, as the next middleware in the pipeline cannot be specified as a parameter. + // To work around this limitation, the server OWIN middleware is manually instantiated and invoked. + var middleware = new OpenIddictServerOwinMiddleware( + next: Next, + options: GetRequiredService>(provider), + dispatcher: GetRequiredService(provider), + factory: GetRequiredService(provider)); - return middleware.Invoke(context); + return middleware.Invoke(context); - static T GetRequiredService(IServiceProvider provider) => provider.GetService() ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0122)); - } + static T GetRequiredService(IServiceProvider provider) => provider.GetService() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0122)); } } diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinOptions.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinOptions.cs index 135ddc1b..7b8ffb9b 100644 --- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinOptions.cs +++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinOptions.cs @@ -10,110 +10,109 @@ using Microsoft.Owin; using Microsoft.Owin.Security; using Owin; -namespace OpenIddict.Server.Owin +namespace OpenIddict.Server.Owin; + +/// +/// Provides various settings needed to configure the OpenIddict OWIN server integration. +/// +public class OpenIddictServerOwinOptions : AuthenticationOptions { /// - /// Provides various settings needed to configure the OpenIddict OWIN server integration. + /// Creates a new instance of the class. /// - public class OpenIddictServerOwinOptions : AuthenticationOptions - { - /// - /// Creates a new instance of the class. - /// - public OpenIddictServerOwinOptions() - : base(OpenIddictServerOwinDefaults.AuthenticationType) - => AuthenticationMode = AuthenticationMode.Passive; + public OpenIddictServerOwinOptions() + : base(OpenIddictServerOwinDefaults.AuthenticationType) + => AuthenticationMode = AuthenticationMode.Passive; - /// - /// Gets or sets a boolean indicating whether incoming requests arriving on insecure endpoints should be rejected. - /// By default, this property is set to false to help mitigate man-in-the-middle attacks. - /// - public bool DisableTransportSecurityRequirement { get; set; } + /// + /// Gets or sets a boolean indicating whether incoming requests arriving on insecure endpoints should be rejected. + /// By default, this property is set to false to help mitigate man-in-the-middle attacks. + /// + public bool DisableTransportSecurityRequirement { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the authorization endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableAuthorizationEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the authorization endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableAuthorizationEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether OpenIddict should allow the rest of the request processing pipeline - /// to be invoked when returning an error from the interactive authorization and logout endpoints. - /// When this option is enabled, special logic must be added to these actions to handle errors, that can be - /// retrieved using - /// - public bool EnableErrorPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether OpenIddict should allow the rest of the request processing pipeline + /// to be invoked when returning an error from the interactive authorization and logout endpoints. + /// When this option is enabled, special logic must be added to these actions to handle errors, that can be + /// retrieved using + /// + public bool EnableErrorPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the authorization endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableLogoutEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the authorization endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableLogoutEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the token endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableTokenEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the token endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableTokenEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the userinfo endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableUserinfoEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the userinfo endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableUserinfoEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the user verification endpoint. - /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. - /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests - /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). - /// - public bool EnableVerificationEndpointPassthrough { get; set; } + /// + /// Gets or sets a boolean indicating whether the pass-through mode is enabled for the user verification endpoint. + /// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict. + /// Once validated, the rest of the request processing pipeline is invoked, so that OpenID Connect requests + /// can be handled at a later stage (in a custom middleware or in a MVC controller, for instance). + /// + public bool EnableVerificationEndpointPassthrough { get; set; } - /// - /// Gets or sets a boolean indicating whether requests received by the authorization endpoint - /// should be cached. When enabled, authorization requests are automatically stored - /// in the distributed cache, which allows flowing large payloads across requests. - /// Enabling this option is recommended when using external authentication providers - /// or when large GET or POST OpenID Connect authorization requests support is required. - /// - public bool EnableAuthorizationRequestCaching { get; set; } + /// + /// Gets or sets a boolean indicating whether requests received by the authorization endpoint + /// should be cached. When enabled, authorization requests are automatically stored + /// in the distributed cache, which allows flowing large payloads across requests. + /// Enabling this option is recommended when using external authentication providers + /// or when large GET or POST OpenID Connect authorization requests support is required. + /// + public bool EnableAuthorizationRequestCaching { get; set; } - /// - /// Gets or sets a boolean indicating whether requests received by the logout endpoint should be cached. - /// When enabled, authorization requests are automatically stored in the distributed cache. - /// - public bool EnableLogoutRequestCaching { get; set; } + /// + /// Gets or sets a boolean indicating whether requests received by the logout endpoint should be cached. + /// When enabled, authorization requests are automatically stored in the distributed cache. + /// + public bool EnableLogoutRequestCaching { get; set; } - /// - /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. - /// - public string? Realm { get; set; } + /// + /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. + /// + public string? Realm { get; set; } - /// - /// Gets or sets the caching policy used by the authorization endpoint. - /// - public DistributedCacheEntryOptions AuthorizationRequestCachingPolicy { get; set; } = new() - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), - SlidingExpiration = TimeSpan.FromMinutes(30) - }; + /// + /// Gets or sets the caching policy used by the authorization endpoint. + /// + public DistributedCacheEntryOptions AuthorizationRequestCachingPolicy { get; set; } = new() + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), + SlidingExpiration = TimeSpan.FromMinutes(30) + }; - /// - /// Gets or sets the caching policy used by the logout endpoint. - /// - public DistributedCacheEntryOptions LogoutRequestCachingPolicy { get; set; } = new() - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), - SlidingExpiration = TimeSpan.FromMinutes(30) - }; - } + /// + /// Gets or sets the caching policy used by the logout endpoint. + /// + public DistributedCacheEntryOptions LogoutRequestCachingPolicy { get; set; } = new() + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), + SlidingExpiration = TimeSpan.FromMinutes(30) + }; } diff --git a/src/OpenIddict.Server/IOpenIddictServerDispatcher.cs b/src/OpenIddict.Server/IOpenIddictServerDispatcher.cs index a444f283..ce88b8f4 100644 --- a/src/OpenIddict.Server/IOpenIddictServerDispatcher.cs +++ b/src/OpenIddict.Server/IOpenIddictServerDispatcher.cs @@ -7,10 +7,9 @@ using System.Threading.Tasks; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public interface IOpenIddictServerDispatcher { - public interface IOpenIddictServerDispatcher - { - ValueTask DispatchAsync(TContext context) where TContext : BaseContext; - } -} \ No newline at end of file + ValueTask DispatchAsync(TContext context) where TContext : BaseContext; +} diff --git a/src/OpenIddict.Server/IOpenIddictServerFactory.cs b/src/OpenIddict.Server/IOpenIddictServerFactory.cs index eddbb9c4..ceda39a6 100644 --- a/src/OpenIddict.Server/IOpenIddictServerFactory.cs +++ b/src/OpenIddict.Server/IOpenIddictServerFactory.cs @@ -6,10 +6,9 @@ using System.Threading.Tasks; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public interface IOpenIddictServerFactory { - public interface IOpenIddictServerFactory - { - ValueTask CreateTransactionAsync(); - } -} \ No newline at end of file + ValueTask CreateTransactionAsync(); +} diff --git a/src/OpenIddict.Server/IOpenIddictServerHandler.cs b/src/OpenIddict.Server/IOpenIddictServerHandler.cs index ed873739..be571906 100644 --- a/src/OpenIddict.Server/IOpenIddictServerHandler.cs +++ b/src/OpenIddict.Server/IOpenIddictServerHandler.cs @@ -7,21 +7,20 @@ using System.Threading.Tasks; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +/// +/// Represents a handler able to process events. +/// +/// The type of the context associated with events handled by this instance. +public interface IOpenIddictServerHandler where TContext : BaseContext { /// - /// Represents a handler able to process events. + /// Processes the event. /// - /// The type of the context associated with events handled by this instance. - public interface IOpenIddictServerHandler where TContext : BaseContext - { - /// - /// Processes the event. - /// - /// The context associated with the event to process. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask HandleAsync(TContext context); - } + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask HandleAsync(TContext context); } diff --git a/src/OpenIddict.Server/IOpenIddictServerHandlerFilter.cs b/src/OpenIddict.Server/IOpenIddictServerHandlerFilter.cs index 3d428602..fa037a09 100644 --- a/src/OpenIddict.Server/IOpenIddictServerHandlerFilter.cs +++ b/src/OpenIddict.Server/IOpenIddictServerHandlerFilter.cs @@ -7,10 +7,9 @@ using System.Threading.Tasks; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public interface IOpenIddictServerHandlerFilter where TContext : BaseContext { - public interface IOpenIddictServerHandlerFilter where TContext : BaseContext - { - ValueTask IsActiveAsync(TContext context); - } + ValueTask IsActiveAsync(TContext context); } diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs index c181ff62..e0920423 100644 --- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs +++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs @@ -19,1852 +19,1851 @@ using OpenIddict.Server; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the OpenIddict server services. +/// +public class OpenIddictServerBuilder { /// - /// Exposes the necessary methods required to configure the OpenIddict server services. + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictServerBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. /// - public class OpenIddictServerBuilder + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Registers an event handler using the specified configuration delegate. + /// + /// The event context type. + /// The configuration delegate. + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictServerBuilder AddEventHandler( + Action> configuration) + where TContext : OpenIddictServerEvents.BaseContext { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictServerBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Registers an event handler using the specified configuration delegate. - /// - /// The event context type. - /// The configuration delegate. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerBuilder AddEventHandler( - Action> configuration) - where TContext : OpenIddictServerEvents.BaseContext - { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - // Note: handlers registered using this API are assumed to be custom handlers by default. - var builder = OpenIddictServerHandlerDescriptor.CreateBuilder() - .SetType(OpenIddictServerHandlerType.Custom); + // Note: handlers registered using this API are assumed to be custom handlers by default. + var builder = OpenIddictServerHandlerDescriptor.CreateBuilder() + .SetType(OpenIddictServerHandlerType.Custom); - configuration(builder); + configuration(builder); - return AddEventHandler(builder.Build()); - } + return AddEventHandler(builder.Build()); + } - /// - /// Registers an event handler using the specified descriptor. - /// - /// The handler descriptor. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerBuilder AddEventHandler(OpenIddictServerHandlerDescriptor descriptor) + /// + /// Registers an event handler using the specified descriptor. + /// + /// The handler descriptor. + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictServerBuilder AddEventHandler(OpenIddictServerHandlerDescriptor descriptor) + { + if (descriptor is null) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - // Register the handler in the services collection. - Services.Add(descriptor.ServiceDescriptor); + // Register the handler in the services collection. + Services.Add(descriptor.ServiceDescriptor); - return Configure(options => options.Handlers.Add(descriptor)); - } + return Configure(options => options.Handlers.Add(descriptor)); + } - /// - /// Removes the event handler that matches the specified descriptor. - /// - /// The descriptor corresponding to the handler to remove. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerBuilder RemoveEventHandler(OpenIddictServerHandlerDescriptor descriptor) + /// + /// Removes the event handler that matches the specified descriptor. + /// + /// The descriptor corresponding to the handler to remove. + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictServerBuilder RemoveEventHandler(OpenIddictServerHandlerDescriptor descriptor) + { + if (descriptor is null) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - Services.RemoveAll(descriptor.ServiceDescriptor.ServiceType); + Services.RemoveAll(descriptor.ServiceDescriptor.ServiceType); - Services.PostConfigure(options => + Services.PostConfigure(options => + { + for (var index = options.Handlers.Count - 1; index >= 0; index--) { - for (var index = options.Handlers.Count - 1; index >= 0; index--) + if (options.Handlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType) { - if (options.Handlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType) - { - options.Handlers.RemoveAt(index); - } + options.Handlers.RemoveAt(index); } - }); + } + }); - return this; - } + return this; + } - /// - /// Amends the default OpenIddict server configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictServerBuilder Configure(Action configuration) + /// + /// Amends the default OpenIddict server configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictServerBuilder Configure(Action configuration) + { + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Makes client identification optional so that token, introspection and revocation - /// requests that don't specify a client_id are not automatically rejected. - /// Enabling this option is NOT recommended. - /// - /// The . - public OpenIddictServerBuilder AcceptAnonymousClients() - => Configure(options => options.AcceptAnonymousClients = true); + /// + /// Makes client identification optional so that token, introspection and revocation + /// requests that don't specify a client_id are not automatically rejected. + /// Enabling this option is NOT recommended. + /// + /// The . + public OpenIddictServerBuilder AcceptAnonymousClients() + => Configure(options => options.AcceptAnonymousClients = true); - /// - /// Registers encryption credentials. - /// - /// The encrypting credentials. - /// The . - public OpenIddictServerBuilder AddEncryptionCredentials(EncryptingCredentials credentials) + /// + /// Registers encryption credentials. + /// + /// The encrypting credentials. + /// The . + public OpenIddictServerBuilder AddEncryptionCredentials(EncryptingCredentials credentials) + { + if (credentials is null) { - if (credentials is null) - { - throw new ArgumentNullException(nameof(credentials)); - } - - return Configure(options => options.EncryptionCredentials.Add(credentials)); + throw new ArgumentNullException(nameof(credentials)); } - /// - /// Registers an encryption key. - /// - /// The security key. - /// The . - public OpenIddictServerBuilder AddEncryptionKey(SecurityKey key) + return Configure(options => options.EncryptionCredentials.Add(credentials)); + } + + /// + /// Registers an encryption key. + /// + /// The security key. + /// The . + public OpenIddictServerBuilder AddEncryptionKey(SecurityKey key) + { + if (key is null) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } + throw new ArgumentNullException(nameof(key)); + } - // If the encryption key is an asymmetric security key, ensure it has a private key. - if (key is AsymmetricSecurityKey asymmetricSecurityKey && - asymmetricSecurityKey.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0055)); - } + // If the encryption key is an asymmetric security key, ensure it has a private key. + if (key is AsymmetricSecurityKey asymmetricSecurityKey && + asymmetricSecurityKey.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0055)); + } - if (key.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)) + if (key.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)) + { + if (key.KeySize != 256) { - if (key.KeySize != 256) - { - throw new InvalidOperationException(SR.FormatID0283(256, key.KeySize)); - } - - return AddEncryptionCredentials(new EncryptingCredentials(key, - SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)); + throw new InvalidOperationException(SR.FormatID0283(256, key.KeySize)); } - if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaOAEP)) - { - return AddEncryptionCredentials(new EncryptingCredentials(key, - SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512)); - } + return AddEncryptionCredentials(new EncryptingCredentials(key, + SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)); + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0056)); + if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaOAEP)) + { + return AddEncryptionCredentials(new EncryptingCredentials(key, + SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512)); } - /// - /// Registers (and generates if necessary) a user-specific development encryption certificate. - /// - /// The . - public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate() - => AddDevelopmentEncryptionCertificate(new X500DistinguishedName("CN=OpenIddict Server Encryption Certificate")); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0056)); + } + + /// + /// Registers (and generates if necessary) a user-specific development encryption certificate. + /// + /// The . + public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate() + => AddDevelopmentEncryptionCertificate(new X500DistinguishedName("CN=OpenIddict Server Encryption Certificate")); - /// - /// Registers (and generates if necessary) a user-specific development encryption certificate. - /// - /// The subject name associated with the certificate. - /// The . - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] - public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject) + /// + /// Registers (and generates if necessary) a user-specific development encryption certificate. + /// + /// The subject name associated with the certificate. + /// The . + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The X.509 certificate is attached to the server options.")] + public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate(X500DistinguishedName subject) + { + if (subject is null) { - if (subject is null) - { - throw new ArgumentNullException(nameof(subject)); - } + throw new ArgumentNullException(nameof(subject)); + } - using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); - store.Open(OpenFlags.ReadWrite); + using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadWrite); - // Try to retrieve the existing development certificates from the specified store. - // If no valid existing certificate was found, create a new encryption certificate. - var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) - .OfType() - .ToList(); + // Try to retrieve the existing development certificates from the specified store. + // If no valid existing certificate was found, create a new encryption certificate. + var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) + .OfType() + .ToList(); - if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) - { + if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) + { #if SUPPORTS_CERTIFICATE_GENERATION - using var algorithm = RSA.Create(keySizeInBits: 2048); - - var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); + using var algorithm = RSA.Create(keySizeInBits: 2048); - var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); - - // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS). - // To ensure an exception is not thrown by the property setter, an OS runtime check is used here. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - certificate.FriendlyName = "OpenIddict Server Development Encryption Certificate"; - } + var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); - // Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate - // as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key. - // To work around this issue, the certificate payload is manually exported and imported back - // into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag. - var data = certificate.Export(X509ContentType.Pfx, string.Empty); + var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); - try - { - var flags = X509KeyStorageFlags.PersistKeySet; + // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS). + // To ensure an exception is not thrown by the property setter, an OS runtime check is used here. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + certificate.FriendlyName = "OpenIddict Server Development Encryption Certificate"; + } - // Note: macOS requires marking the certificate private key as exportable. - // If this flag is not set, a CryptographicException is thrown at runtime. - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - flags |= X509KeyStorageFlags.Exportable; - } + // Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate + // as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key. + // To work around this issue, the certificate payload is manually exported and imported back + // into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag. + var data = certificate.Export(X509ContentType.Pfx, string.Empty); - certificates.Insert(0, certificate = new X509Certificate2(data, string.Empty, flags)); - } + try + { + var flags = X509KeyStorageFlags.PersistKeySet; - finally + // Note: macOS requires marking the certificate private key as exportable. + // If this flag is not set, a CryptographicException is thrown at runtime. + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - Array.Clear(data, 0, data.Length); + flags |= X509KeyStorageFlags.Exportable; } - store.Add(certificate); -#else - throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); -#endif + certificates.Insert(0, certificate = new X509Certificate2(data, string.Empty, flags)); } - return Configure(options => options.EncryptionCredentials.AddRange( - from certificate in certificates - let key = new X509SecurityKey(certificate) - select new EncryptingCredentials(key, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512))); - } - - /// - /// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically - /// discarded when the application shuts down and payloads encrypted using this key are - /// automatically invalidated. This method should only be used during development. - /// On production, using a X.509 certificate stored in the machine store is recommended. - /// - /// The . - public OpenIddictServerBuilder AddEphemeralEncryptionKey() - => AddEphemeralEncryptionKey(SecurityAlgorithms.RsaOAEP); - - /// - /// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically - /// discarded when the application shuts down and payloads encrypted using this key are - /// automatically invalidated. This method should only be used during development. - /// On production, using a X.509 certificate stored in the machine store is recommended. - /// - /// The algorithm associated with the encryption key. - /// The . - public OpenIddictServerBuilder AddEphemeralEncryptionKey(string algorithm) - { - if (string.IsNullOrEmpty(algorithm)) + finally { - throw new ArgumentException(SR.GetResourceString(SR.ID0057), nameof(algorithm)); + Array.Clear(data, 0, data.Length); } - return algorithm switch - { - SecurityAlgorithms.Aes256KW - => AddEncryptionCredentials(new EncryptingCredentials(CreateSymmetricSecurityKey(256), - algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), - - SecurityAlgorithms.RsaOAEP or - SecurityAlgorithms.RsaOaepKeyWrap - => AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), - algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), + store.Add(certificate); +#else + throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); +#endif + } - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)), - }; + return Configure(options => options.EncryptionCredentials.AddRange( + from certificate in certificates + let key = new X509SecurityKey(certificate) + select new EncryptingCredentials(key, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512))); + } - static SymmetricSecurityKey CreateSymmetricSecurityKey(int size) - { - var data = new byte[size / 8]; + /// + /// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically + /// discarded when the application shuts down and payloads encrypted using this key are + /// automatically invalidated. This method should only be used during development. + /// On production, using a X.509 certificate stored in the machine store is recommended. + /// + /// The . + public OpenIddictServerBuilder AddEphemeralEncryptionKey() + => AddEphemeralEncryptionKey(SecurityAlgorithms.RsaOAEP); -#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); -#else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); -#endif + /// + /// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically + /// discarded when the application shuts down and payloads encrypted using this key are + /// automatically invalidated. This method should only be used during development. + /// On production, using a X.509 certificate stored in the machine store is recommended. + /// + /// The algorithm associated with the encryption key. + /// The . + public OpenIddictServerBuilder AddEphemeralEncryptionKey(string algorithm) + { + if (string.IsNullOrEmpty(algorithm)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0057), nameof(algorithm)); + } - return new SymmetricSecurityKey(data); - } + return algorithm switch + { + SecurityAlgorithms.Aes256KW + => AddEncryptionCredentials(new EncryptingCredentials(CreateSymmetricSecurityKey(256), + algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The generated RSA key is attached to the server options.")] - static RsaSecurityKey CreateRsaSecurityKey(int size) - { -#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE - return new RsaSecurityKey(RSA.Create(size)); -#else - // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, - // where RSACryptoServiceProvider is still the default implementation and - // where custom implementations can be registered via CryptoConfig. - // To ensure the key size is always acceptable, replace it if necessary. - var algorithm = RSA.Create(); - if (algorithm.KeySize < size) - { - algorithm.KeySize = size; - } + SecurityAlgorithms.RsaOAEP or + SecurityAlgorithms.RsaOaepKeyWrap + => AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), + algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)), - if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) - { - algorithm.Dispose(); - algorithm = new RSACryptoServiceProvider(size); - } + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)), + }; - if (algorithm.KeySize < size) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0059)); - } + static SymmetricSecurityKey CreateSymmetricSecurityKey(int size) + { + var data = new byte[size / 8]; - return new RsaSecurityKey(algorithm); +#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS + RandomNumberGenerator.Fill(data); +#else + using var generator = RandomNumberGenerator.Create(); + generator.GetBytes(data); #endif - } + + return new SymmetricSecurityKey(data); } - /// - /// Registers an encryption certificate. - /// - /// The encryption certificate. - /// The . - public OpenIddictServerBuilder AddEncryptionCertificate(X509Certificate2 certificate) + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The generated RSA key is attached to the server options.")] + static RsaSecurityKey CreateRsaSecurityKey(int size) { - if (certificate is null) +#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE + return new RsaSecurityKey(RSA.Create(size)); +#else + // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, + // where RSACryptoServiceProvider is still the default implementation and + // where custom implementations can be registered via CryptoConfig. + // To ensure the key size is always acceptable, replace it if necessary. + var algorithm = RSA.Create(); + if (algorithm.KeySize < size) { - throw new ArgumentNullException(nameof(certificate)); + algorithm.KeySize = size; } - // If the certificate is a X.509v3 certificate that specifies at least one - // key usage, ensure that the certificate key can be used for key encryption. - if (certificate.Version >= 3) + if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) { - var extensions = certificate.Extensions.OfType().ToList(); - if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.KeyEncipherment))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0060)); - } + algorithm.Dispose(); + algorithm = new RSACryptoServiceProvider(size); } - if (!certificate.HasPrivateKey) + if (algorithm.KeySize < size) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0061)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0059)); } - return AddEncryptionKey(new X509SecurityKey(certificate)); - } - - /// - /// Registers an encryption certificate retrieved from an embedded resource. - /// - /// The assembly containing the certificate. - /// The name of the embedded resource. - /// The password used to open the certificate. - /// The . - public OpenIddictServerBuilder AddEncryptionCertificate(Assembly assembly, string resource, string? password) -#if SUPPORTS_EPHEMERAL_KEY_SETS - // Note: ephemeral key sets are currently not supported on macOS. - => AddEncryptionCertificate(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? - X509KeyStorageFlags.MachineKeySet : - X509KeyStorageFlags.EphemeralKeySet); -#else - => AddEncryptionCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet); + return new RsaSecurityKey(algorithm); #endif + } + } - /// - /// Registers an encryption certificate retrieved from an embedded resource. - /// - /// The assembly containing the certificate. - /// The name of the embedded resource. - /// The password used to open the certificate. - /// An enumeration of flags indicating how and where to store the private key of the certificate. - /// The . - public OpenIddictServerBuilder AddEncryptionCertificate( - Assembly assembly, string resource, - string? password, X509KeyStorageFlags flags) - { - if (assembly is null) - { - throw new ArgumentNullException(nameof(assembly)); - } - - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); - } + /// + /// Registers an encryption certificate. + /// + /// The encryption certificate. + /// The . + public OpenIddictServerBuilder AddEncryptionCertificate(X509Certificate2 certificate) + { + if (certificate is null) + { + throw new ArgumentNullException(nameof(certificate)); + } - using var stream = assembly.GetManifestResourceStream(resource); - if (stream is null) + // If the certificate is a X.509v3 certificate that specifies at least one + // key usage, ensure that the certificate key can be used for key encryption. + if (certificate.Version >= 3) + { + var extensions = certificate.Extensions.OfType().ToList(); + if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.KeyEncipherment))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0064)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0060)); } + } - return AddEncryptionCertificate(stream, password, flags); + if (!certificate.HasPrivateKey) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0061)); } - /// - /// Registers an encryption certificate extracted from a stream. - /// - /// The stream containing the certificate. - /// The password used to open the certificate. - /// The . - public OpenIddictServerBuilder AddEncryptionCertificate(Stream stream, string? password) + return AddEncryptionKey(new X509SecurityKey(certificate)); + } + + /// + /// Registers an encryption certificate retrieved from an embedded resource. + /// + /// The assembly containing the certificate. + /// The name of the embedded resource. + /// The password used to open the certificate. + /// The . + public OpenIddictServerBuilder AddEncryptionCertificate(Assembly assembly, string resource, string? password) #if SUPPORTS_EPHEMERAL_KEY_SETS - // Note: ephemeral key sets are currently not supported on macOS. - => AddEncryptionCertificate(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? - X509KeyStorageFlags.MachineKeySet : - X509KeyStorageFlags.EphemeralKeySet); + // Note: ephemeral key sets are currently not supported on macOS. + => AddEncryptionCertificate(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + X509KeyStorageFlags.MachineKeySet : + X509KeyStorageFlags.EphemeralKeySet); #else - => AddEncryptionCertificate(stream, password, X509KeyStorageFlags.MachineKeySet); + => AddEncryptionCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet); #endif - /// - /// Registers an encryption certificate extracted from a stream. - /// - /// The stream containing the certificate. - /// The password used to open the certificate. - /// - /// An enumeration of flags indicating how and where - /// to store the private key of the certificate. - /// - /// The . - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] - public OpenIddictServerBuilder AddEncryptionCertificate(Stream stream, string? password, X509KeyStorageFlags flags) + /// + /// Registers an encryption certificate retrieved from an embedded resource. + /// + /// The assembly containing the certificate. + /// The name of the embedded resource. + /// The password used to open the certificate. + /// An enumeration of flags indicating how and where to store the private key of the certificate. + /// The . + public OpenIddictServerBuilder AddEncryptionCertificate( + Assembly assembly, string resource, + string? password, X509KeyStorageFlags flags) + { + if (assembly is null) { - if (stream is null) - { - throw new ArgumentNullException(nameof(stream)); - } - - using var buffer = new MemoryStream(); - stream.CopyTo(buffer); + throw new ArgumentNullException(nameof(assembly)); + } - return AddEncryptionCertificate(new X509Certificate2(buffer.ToArray(), password, flags)); + if (string.IsNullOrEmpty(resource)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); } - /// - /// Registers an encryption certificate retrieved from the X.509 user or machine store. - /// - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The . - public OpenIddictServerBuilder AddEncryptionCertificate(string thumbprint) + using var stream = assembly.GetManifestResourceStream(resource); + if (stream is null) { - if (string.IsNullOrEmpty(thumbprint)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0064)); + } - var certificate = GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint); - if (certificate is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); - } + return AddEncryptionCertificate(stream, password, flags); + } - return AddEncryptionCertificate(certificate); + /// + /// Registers an encryption certificate extracted from a stream. + /// + /// The stream containing the certificate. + /// The password used to open the certificate. + /// The . + public OpenIddictServerBuilder AddEncryptionCertificate(Stream stream, string? password) +#if SUPPORTS_EPHEMERAL_KEY_SETS + // Note: ephemeral key sets are currently not supported on macOS. + => AddEncryptionCertificate(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + X509KeyStorageFlags.MachineKeySet : + X509KeyStorageFlags.EphemeralKeySet); +#else + => AddEncryptionCertificate(stream, password, X509KeyStorageFlags.MachineKeySet); +#endif - static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint) - { - using var store = new X509Store(StoreName.My, location); - store.Open(OpenFlags.ReadOnly); + /// + /// Registers an encryption certificate extracted from a stream. + /// + /// The stream containing the certificate. + /// The password used to open the certificate. + /// + /// An enumeration of flags indicating how and where + /// to store the private key of the certificate. + /// + /// The . + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The X.509 certificate is attached to the server options.")] + public OpenIddictServerBuilder AddEncryptionCertificate(Stream stream, string? password, X509KeyStorageFlags flags) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } - return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) - .OfType() - .SingleOrDefault(); - } + using var buffer = new MemoryStream(); + stream.CopyTo(buffer); + + return AddEncryptionCertificate(new X509Certificate2(buffer.ToArray(), password, flags)); + } + + /// + /// Registers an encryption certificate retrieved from the X.509 user or machine store. + /// + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The . + public OpenIddictServerBuilder AddEncryptionCertificate(string thumbprint) + { + if (string.IsNullOrEmpty(thumbprint)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); } - /// - /// Registers an encryption certificate retrieved from the specified X.509 store. - /// - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The name of the X.509 store. - /// The location of the X.509 store. - /// The . - public OpenIddictServerBuilder AddEncryptionCertificate(string thumbprint, StoreName name, StoreLocation location) + var certificate = GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint); + if (certificate is null) { - if (string.IsNullOrEmpty(thumbprint)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); + } - using var store = new X509Store(name, location); + return AddEncryptionCertificate(certificate); + + static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint) + { + using var store = new X509Store(StoreName.My, location); store.Open(OpenFlags.ReadOnly); - var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) + return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) .OfType() .SingleOrDefault(); + } + } - if (certificate is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); - } - - return AddEncryptionCertificate(certificate); + /// + /// Registers an encryption certificate retrieved from the specified X.509 store. + /// + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The name of the X.509 store. + /// The location of the X.509 store. + /// The . + public OpenIddictServerBuilder AddEncryptionCertificate(string thumbprint, StoreName name, StoreLocation location) + { + if (string.IsNullOrEmpty(thumbprint)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); } - /// - /// Registers signing credentials. - /// - /// The signing credentials. - /// The . - public OpenIddictServerBuilder AddSigningCredentials(SigningCredentials credentials) + using var store = new X509Store(name, location); + store.Open(OpenFlags.ReadOnly); + + var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) + .OfType() + .SingleOrDefault(); + + if (certificate is null) { - if (credentials is null) - { - throw new ArgumentNullException(nameof(credentials)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); + } + + return AddEncryptionCertificate(certificate); + } - return Configure(options => options.SigningCredentials.Add(credentials)); + /// + /// Registers signing credentials. + /// + /// The signing credentials. + /// The . + public OpenIddictServerBuilder AddSigningCredentials(SigningCredentials credentials) + { + if (credentials is null) + { + throw new ArgumentNullException(nameof(credentials)); } - /// - /// Registers a signing key. - /// - /// The security key. - /// The . - public OpenIddictServerBuilder AddSigningKey(SecurityKey key) + return Configure(options => options.SigningCredentials.Add(credentials)); + } + + /// + /// Registers a signing key. + /// + /// The security key. + /// The . + public OpenIddictServerBuilder AddSigningKey(SecurityKey key) + { + if (key is null) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } + throw new ArgumentNullException(nameof(key)); + } - // If the signing key is an asymmetric security key, ensure it has a private key. - if (key is AsymmetricSecurityKey asymmetricSecurityKey && - asymmetricSecurityKey.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0067)); - } + // If the signing key is an asymmetric security key, ensure it has a private key. + if (key is AsymmetricSecurityKey asymmetricSecurityKey && + asymmetricSecurityKey.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0067)); + } - if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256)) - { - return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.RsaSha256)); - } + if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256)) + { + return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.RsaSha256)); + } - if (key.IsSupportedAlgorithm(SecurityAlgorithms.HmacSha256)) - { - return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)); - } + if (key.IsSupportedAlgorithm(SecurityAlgorithms.HmacSha256)) + { + return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)); + } #if SUPPORTS_ECDSA - // Note: ECDSA algorithms are bound to specific curves and must be treated separately. - if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256)) - { - return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256)); - } + // Note: ECDSA algorithms are bound to specific curves and must be treated separately. + if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256)) + { + return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256)); + } - if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384)) - { - return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha384)); - } + if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384)) + { + return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha384)); + } - if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) - { - return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha512)); - } + if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) + { + return AddSigningCredentials(new SigningCredentials(key, SecurityAlgorithms.EcdsaSha512)); + } #else - if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) || - key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) || - key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) - { - throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)); - } + if (key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) || + key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) || + key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) + { + throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)); + } #endif - throw new InvalidOperationException(SR.GetResourceString(SR.ID0068)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0068)); + } - /// - /// Registers (and generates if necessary) a user-specific development signing certificate. - /// - /// The . - public OpenIddictServerBuilder AddDevelopmentSigningCertificate() - => AddDevelopmentSigningCertificate(new X500DistinguishedName("CN=OpenIddict Server Signing Certificate")); + /// + /// Registers (and generates if necessary) a user-specific development signing certificate. + /// + /// The . + public OpenIddictServerBuilder AddDevelopmentSigningCertificate() + => AddDevelopmentSigningCertificate(new X500DistinguishedName("CN=OpenIddict Server Signing Certificate")); - /// - /// Registers (and generates if necessary) a user-specific development signing certificate. - /// - /// The subject name associated with the certificate. - /// The . - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] - public OpenIddictServerBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject) + /// + /// Registers (and generates if necessary) a user-specific development signing certificate. + /// + /// The subject name associated with the certificate. + /// The . + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The X.509 certificate is attached to the server options.")] + public OpenIddictServerBuilder AddDevelopmentSigningCertificate(X500DistinguishedName subject) + { + if (subject is null) { - if (subject is null) - { - throw new ArgumentNullException(nameof(subject)); - } + throw new ArgumentNullException(nameof(subject)); + } - using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); - store.Open(OpenFlags.ReadWrite); + using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadWrite); - // Try to retrieve the existing development certificates from the specified store. - // If no valid existing certificate was found, create a new signing certificate. - var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) - .OfType() - .ToList(); + // Try to retrieve the existing development certificates from the specified store. + // If no valid existing certificate was found, create a new signing certificate. + var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) + .OfType() + .ToList(); - if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) - { + if (!certificates.Any(certificate => certificate.NotBefore < DateTime.Now && certificate.NotAfter > DateTime.Now)) + { #if SUPPORTS_CERTIFICATE_GENERATION - using var algorithm = RSA.Create(keySizeInBits: 2048); - - var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); - - var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); + using var algorithm = RSA.Create(keySizeInBits: 2048); - // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS). - // To ensure an exception is not thrown by the property setter, an OS runtime check is used here. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - certificate.FriendlyName = "OpenIddict Server Development Signing Certificate"; - } + var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); - // Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate - // as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key. - // To work around this issue, the certificate payload is manually exported and imported back - // into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag. - var data = certificate.Export(X509ContentType.Pfx, string.Empty); + var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); - try - { - var flags = X509KeyStorageFlags.PersistKeySet; + // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS). + // To ensure an exception is not thrown by the property setter, an OS runtime check is used here. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + certificate.FriendlyName = "OpenIddict Server Development Signing Certificate"; + } - // Note: macOS requires marking the certificate private key as exportable. - // If this flag is not set, a CryptographicException is thrown at runtime. - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - flags |= X509KeyStorageFlags.Exportable; - } + // Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate + // as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key. + // To work around this issue, the certificate payload is manually exported and imported back + // into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag. + var data = certificate.Export(X509ContentType.Pfx, string.Empty); - certificates.Insert(0, certificate = new X509Certificate2(data, string.Empty, flags)); - } + try + { + var flags = X509KeyStorageFlags.PersistKeySet; - finally + // Note: macOS requires marking the certificate private key as exportable. + // If this flag is not set, a CryptographicException is thrown at runtime. + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - Array.Clear(data, 0, data.Length); + flags |= X509KeyStorageFlags.Exportable; } - store.Add(certificate); -#else - throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); -#endif + certificates.Insert(0, certificate = new X509Certificate2(data, string.Empty, flags)); } - return Configure(options => options.SigningCredentials.AddRange( - from certificate in certificates - let key = new X509SecurityKey(certificate) - select new SigningCredentials(key, SecurityAlgorithms.RsaSha256))); - } - - /// - /// Registers a new ephemeral signing key. Ephemeral signing keys are automatically - /// discarded when the application shuts down and payloads signed using this key are - /// automatically invalidated. This method should only be used during development. - /// On production, using a X.509 certificate stored in the machine store is recommended. - /// - /// The . - public OpenIddictServerBuilder AddEphemeralSigningKey() - => AddEphemeralSigningKey(SecurityAlgorithms.RsaSha256); - - /// - /// Registers a new ephemeral signing key. Ephemeral signing keys are automatically - /// discarded when the application shuts down and payloads signed using this key are - /// automatically invalidated. This method should only be used during development. - /// On production, using a X.509 certificate stored in the machine store is recommended. - /// - /// The algorithm associated with the signing key. - /// The . - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] - public OpenIddictServerBuilder AddEphemeralSigningKey(string algorithm) - { - if (string.IsNullOrEmpty(algorithm)) + finally { - throw new ArgumentException(SR.GetResourceString(SR.ID0057), nameof(algorithm)); + Array.Clear(data, 0, data.Length); } - return algorithm switch - { - SecurityAlgorithms.RsaSha256 or - SecurityAlgorithms.RsaSha384 or - SecurityAlgorithms.RsaSha512 or - SecurityAlgorithms.RsaSha256Signature or - SecurityAlgorithms.RsaSha384Signature or - SecurityAlgorithms.RsaSha512Signature or - SecurityAlgorithms.RsaSsaPssSha256 or - SecurityAlgorithms.RsaSsaPssSha384 or - SecurityAlgorithms.RsaSsaPssSha512 or - SecurityAlgorithms.RsaSsaPssSha256Signature or - SecurityAlgorithms.RsaSsaPssSha384Signature or - SecurityAlgorithms.RsaSsaPssSha512Signature - => AddSigningCredentials(new SigningCredentials(CreateRsaSecurityKey(2048), algorithm)), - -#if SUPPORTS_ECDSA - SecurityAlgorithms.EcdsaSha256 or - SecurityAlgorithms.EcdsaSha256Signature - => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( - ECDsa.Create(ECCurve.NamedCurves.nistP256)), algorithm)), - - SecurityAlgorithms.EcdsaSha384 or - SecurityAlgorithms.EcdsaSha384Signature - => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( - ECDsa.Create(ECCurve.NamedCurves.nistP384)), algorithm)), - - SecurityAlgorithms.EcdsaSha512 or - SecurityAlgorithms.EcdsaSha512Signature - => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( - ECDsa.Create(ECCurve.NamedCurves.nistP521)), algorithm)), + store.Add(certificate); #else - SecurityAlgorithms.EcdsaSha256 or - SecurityAlgorithms.EcdsaSha384 or - SecurityAlgorithms.EcdsaSha512 or - SecurityAlgorithms.EcdsaSha256Signature or - SecurityAlgorithms.EcdsaSha384Signature or - SecurityAlgorithms.EcdsaSha512Signature - => throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)), + throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0264)); #endif + } - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)), - }; + return Configure(options => options.SigningCredentials.AddRange( + from certificate in certificates + let key = new X509SecurityKey(certificate) + select new SigningCredentials(key, SecurityAlgorithms.RsaSha256))); + } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The generated RSA key is attached to the server options.")] - static RsaSecurityKey CreateRsaSecurityKey(int size) - { -#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE - return new RsaSecurityKey(RSA.Create(size)); -#else - // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, - // where RSACryptoServiceProvider is still the default implementation and - // where custom implementations can be registered via CryptoConfig. - // To ensure the key size is always acceptable, replace it if necessary. - var algorithm = RSA.Create(); - if (algorithm.KeySize < size) - { - algorithm.KeySize = size; - } + /// + /// Registers a new ephemeral signing key. Ephemeral signing keys are automatically + /// discarded when the application shuts down and payloads signed using this key are + /// automatically invalidated. This method should only be used during development. + /// On production, using a X.509 certificate stored in the machine store is recommended. + /// + /// The . + public OpenIddictServerBuilder AddEphemeralSigningKey() + => AddEphemeralSigningKey(SecurityAlgorithms.RsaSha256); - if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) - { - algorithm.Dispose(); - algorithm = new RSACryptoServiceProvider(size); - } + /// + /// Registers a new ephemeral signing key. Ephemeral signing keys are automatically + /// discarded when the application shuts down and payloads signed using this key are + /// automatically invalidated. This method should only be used during development. + /// On production, using a X.509 certificate stored in the machine store is recommended. + /// + /// The algorithm associated with the signing key. + /// The . + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The X.509 certificate is attached to the server options.")] + public OpenIddictServerBuilder AddEphemeralSigningKey(string algorithm) + { + if (string.IsNullOrEmpty(algorithm)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0057), nameof(algorithm)); + } - if (algorithm.KeySize < size) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0059)); - } + return algorithm switch + { + SecurityAlgorithms.RsaSha256 or + SecurityAlgorithms.RsaSha384 or + SecurityAlgorithms.RsaSha512 or + SecurityAlgorithms.RsaSha256Signature or + SecurityAlgorithms.RsaSha384Signature or + SecurityAlgorithms.RsaSha512Signature or + SecurityAlgorithms.RsaSsaPssSha256 or + SecurityAlgorithms.RsaSsaPssSha384 or + SecurityAlgorithms.RsaSsaPssSha512 or + SecurityAlgorithms.RsaSsaPssSha256Signature or + SecurityAlgorithms.RsaSsaPssSha384Signature or + SecurityAlgorithms.RsaSsaPssSha512Signature + => AddSigningCredentials(new SigningCredentials(CreateRsaSecurityKey(2048), algorithm)), - return new RsaSecurityKey(algorithm); +#if SUPPORTS_ECDSA + SecurityAlgorithms.EcdsaSha256 or + SecurityAlgorithms.EcdsaSha256Signature + => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( + ECDsa.Create(ECCurve.NamedCurves.nistP256)), algorithm)), + + SecurityAlgorithms.EcdsaSha384 or + SecurityAlgorithms.EcdsaSha384Signature + => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( + ECDsa.Create(ECCurve.NamedCurves.nistP384)), algorithm)), + + SecurityAlgorithms.EcdsaSha512 or + SecurityAlgorithms.EcdsaSha512Signature + => AddSigningCredentials(new SigningCredentials(new ECDsaSecurityKey( + ECDsa.Create(ECCurve.NamedCurves.nistP521)), algorithm)), +#else + SecurityAlgorithms.EcdsaSha256 or + SecurityAlgorithms.EcdsaSha384 or + SecurityAlgorithms.EcdsaSha512 or + SecurityAlgorithms.EcdsaSha256Signature or + SecurityAlgorithms.EcdsaSha384Signature or + SecurityAlgorithms.EcdsaSha512Signature + => throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0069)), #endif - } - } - /// - /// Registers a signing certificate. - /// - /// The signing certificate. - /// The . - public OpenIddictServerBuilder AddSigningCertificate(X509Certificate2 certificate) + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0058)), + }; + + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The generated RSA key is attached to the server options.")] + static RsaSecurityKey CreateRsaSecurityKey(int size) { - if (certificate is null) +#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE + return new RsaSecurityKey(RSA.Create(size)); +#else + // Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono, + // where RSACryptoServiceProvider is still the default implementation and + // where custom implementations can be registered via CryptoConfig. + // To ensure the key size is always acceptable, replace it if necessary. + var algorithm = RSA.Create(); + if (algorithm.KeySize < size) { - throw new ArgumentNullException(nameof(certificate)); + algorithm.KeySize = size; } - // If the certificate is a X.509v3 certificate that specifies at least - // one key usage, ensure that the certificate key can be used for signing. - if (certificate.Version >= 3) + if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) { - var extensions = certificate.Extensions.OfType().ToList(); - if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.DigitalSignature))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0070)); - } + algorithm.Dispose(); + algorithm = new RSACryptoServiceProvider(size); } - if (!certificate.HasPrivateKey) + if (algorithm.KeySize < size) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0061)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0059)); } - return AddSigningKey(new X509SecurityKey(certificate)); - } - - /// - /// Registers a signing certificate retrieved from an embedded resource. - /// - /// The assembly containing the certificate. - /// The name of the embedded resource. - /// The password used to open the certificate. - /// The . - public OpenIddictServerBuilder AddSigningCertificate(Assembly assembly, string resource, string? password) -#if SUPPORTS_EPHEMERAL_KEY_SETS - // Note: ephemeral key sets are currently not supported on macOS. - => AddSigningCertificate(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? - X509KeyStorageFlags.MachineKeySet : - X509KeyStorageFlags.EphemeralKeySet); -#else - => AddSigningCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet); + return new RsaSecurityKey(algorithm); #endif + } + } - /// - /// Registers a signing certificate retrieved from an embedded resource. - /// - /// The assembly containing the certificate. - /// The name of the embedded resource. - /// The password used to open the certificate. - /// An enumeration of flags indicating how and where to store the private key of the certificate. - /// The . - public OpenIddictServerBuilder AddSigningCertificate( - Assembly assembly, string resource, - string? password, X509KeyStorageFlags flags) - { - if (assembly is null) - { - throw new ArgumentNullException(nameof(assembly)); - } - - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); - } + /// + /// Registers a signing certificate. + /// + /// The signing certificate. + /// The . + public OpenIddictServerBuilder AddSigningCertificate(X509Certificate2 certificate) + { + if (certificate is null) + { + throw new ArgumentNullException(nameof(certificate)); + } - using var stream = assembly.GetManifestResourceStream(resource); - if (stream is null) + // If the certificate is a X.509v3 certificate that specifies at least + // one key usage, ensure that the certificate key can be used for signing. + if (certificate.Version >= 3) + { + var extensions = certificate.Extensions.OfType().ToList(); + if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.DigitalSignature))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0064)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0070)); } + } - return AddSigningCertificate(stream, password, flags); + if (!certificate.HasPrivateKey) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0061)); } - /// - /// Registers a signing certificate extracted from a stream. - /// - /// The stream containing the certificate. - /// The password used to open the certificate. - /// The . - public OpenIddictServerBuilder AddSigningCertificate(Stream stream, string? password) + return AddSigningKey(new X509SecurityKey(certificate)); + } + + /// + /// Registers a signing certificate retrieved from an embedded resource. + /// + /// The assembly containing the certificate. + /// The name of the embedded resource. + /// The password used to open the certificate. + /// The . + public OpenIddictServerBuilder AddSigningCertificate(Assembly assembly, string resource, string? password) #if SUPPORTS_EPHEMERAL_KEY_SETS - // Note: ephemeral key sets are currently not supported on macOS. - => AddSigningCertificate(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? - X509KeyStorageFlags.MachineKeySet : - X509KeyStorageFlags.EphemeralKeySet); + // Note: ephemeral key sets are currently not supported on macOS. + => AddSigningCertificate(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + X509KeyStorageFlags.MachineKeySet : + X509KeyStorageFlags.EphemeralKeySet); #else - => AddSigningCertificate(stream, password, X509KeyStorageFlags.MachineKeySet); + => AddSigningCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet); #endif - /// - /// Registers a signing certificate extracted from a stream. - /// - /// The stream containing the certificate. - /// The password used to open the certificate. - /// - /// An enumeration of flags indicating how and where - /// to store the private key of the certificate. - /// - /// The . - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] - public OpenIddictServerBuilder AddSigningCertificate(Stream stream, string? password, X509KeyStorageFlags flags) + /// + /// Registers a signing certificate retrieved from an embedded resource. + /// + /// The assembly containing the certificate. + /// The name of the embedded resource. + /// The password used to open the certificate. + /// An enumeration of flags indicating how and where to store the private key of the certificate. + /// The . + public OpenIddictServerBuilder AddSigningCertificate( + Assembly assembly, string resource, + string? password, X509KeyStorageFlags flags) + { + if (assembly is null) { - if (stream is null) - { - throw new ArgumentNullException(nameof(stream)); - } - - using var buffer = new MemoryStream(); - stream.CopyTo(buffer); + throw new ArgumentNullException(nameof(assembly)); + } - return AddSigningCertificate(new X509Certificate2(buffer.ToArray(), password, flags)); + if (string.IsNullOrEmpty(resource)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); } - /// - /// Registers a signing certificate retrieved from the X.509 user or machine store. - /// - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The . - public OpenIddictServerBuilder AddSigningCertificate(string thumbprint) + using var stream = assembly.GetManifestResourceStream(resource); + if (stream is null) { - if (string.IsNullOrEmpty(thumbprint)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0064)); + } - var certificate = GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint); - if (certificate is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); - } + return AddSigningCertificate(stream, password, flags); + } + + /// + /// Registers a signing certificate extracted from a stream. + /// + /// The stream containing the certificate. + /// The password used to open the certificate. + /// The . + public OpenIddictServerBuilder AddSigningCertificate(Stream stream, string? password) +#if SUPPORTS_EPHEMERAL_KEY_SETS + // Note: ephemeral key sets are currently not supported on macOS. + => AddSigningCertificate(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + X509KeyStorageFlags.MachineKeySet : + X509KeyStorageFlags.EphemeralKeySet); +#else + => AddSigningCertificate(stream, password, X509KeyStorageFlags.MachineKeySet); +#endif - return AddSigningCertificate(certificate); + /// + /// Registers a signing certificate extracted from a stream. + /// + /// The stream containing the certificate. + /// The password used to open the certificate. + /// + /// An enumeration of flags indicating how and where + /// to store the private key of the certificate. + /// + /// The . + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The X.509 certificate is attached to the server options.")] + public OpenIddictServerBuilder AddSigningCertificate(Stream stream, string? password, X509KeyStorageFlags flags) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } - static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint) - { - using var store = new X509Store(StoreName.My, location); - store.Open(OpenFlags.ReadOnly); + using var buffer = new MemoryStream(); + stream.CopyTo(buffer); - return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) - .OfType() - .SingleOrDefault(); - } + return AddSigningCertificate(new X509Certificate2(buffer.ToArray(), password, flags)); + } + + /// + /// Registers a signing certificate retrieved from the X.509 user or machine store. + /// + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The . + public OpenIddictServerBuilder AddSigningCertificate(string thumbprint) + { + if (string.IsNullOrEmpty(thumbprint)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); } - /// - /// Registers a signing certificate retrieved from the specified X.509 store. - /// - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The name of the X.509 store. - /// The location of the X.509 store. - /// The . - public OpenIddictServerBuilder AddSigningCertificate(string thumbprint, StoreName name, StoreLocation location) + var certificate = GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint); + if (certificate is null) { - if (string.IsNullOrEmpty(thumbprint)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); + } - using var store = new X509Store(name, location); + return AddSigningCertificate(certificate); + + static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint) + { + using var store = new X509Store(StoreName.My, location); store.Open(OpenFlags.ReadOnly); - var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) + return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) .OfType() .SingleOrDefault(); + } + } - if (certificate is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); - } + /// + /// Registers a signing certificate retrieved from the specified X.509 store. + /// + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The name of the X.509 store. + /// The location of the X.509 store. + /// The . + public OpenIddictServerBuilder AddSigningCertificate(string thumbprint, StoreName name, StoreLocation location) + { + if (string.IsNullOrEmpty(thumbprint)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); + } + + using var store = new X509Store(name, location); + store.Open(OpenFlags.ReadOnly); + + var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) + .OfType() + .SingleOrDefault(); - return AddSigningCertificate(certificate); + if (certificate is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); } - /// - /// Enables authorization code flow support. For more information - /// about this specific OAuth 2.0/OpenID Connect flow, visit - /// https://tools.ietf.org/html/rfc6749#section-4.1 and - /// http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. - /// - /// The . - public OpenIddictServerBuilder AllowAuthorizationCodeFlow() - => Configure(options => - { - options.CodeChallengeMethods.Add(CodeChallengeMethods.Sha256); - - options.GrantTypes.Add(GrantTypes.AuthorizationCode); - - options.ResponseModes.Add(ResponseModes.FormPost); - options.ResponseModes.Add(ResponseModes.Fragment); - options.ResponseModes.Add(ResponseModes.Query); - - options.ResponseTypes.Add(ResponseTypes.Code); - }); - - /// - /// Enables client credentials flow support. For more information about this - /// specific OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc6749#section-4.4. - /// - /// The . - public OpenIddictServerBuilder AllowClientCredentialsFlow() - => Configure(options => options.GrantTypes.Add(GrantTypes.ClientCredentials)); - - /// - /// Enables custom grant type support. - /// - /// The grant type associated with the flow. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerBuilder AllowCustomFlow(string type) - { - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0071), nameof(type)); - } + return AddSigningCertificate(certificate); + } - return Configure(options => options.GrantTypes.Add(type)); - } - - /// - /// Enables device code flow support. For more information about this - /// specific OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc8628. - /// - /// The . - public OpenIddictServerBuilder AllowDeviceCodeFlow() - => Configure(options => options.GrantTypes.Add(GrantTypes.DeviceCode)); - - /// - /// Enables hybrid flow support. For more information - /// about this specific OpenID Connect flow, visit - /// http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth. - /// - /// The . - public OpenIddictServerBuilder AllowHybridFlow() - => Configure(options => - { - options.CodeChallengeMethods.Add(CodeChallengeMethods.Sha256); - - options.GrantTypes.Add(GrantTypes.AuthorizationCode); - options.GrantTypes.Add(GrantTypes.Implicit); - - options.ResponseModes.Add(ResponseModes.FormPost); - options.ResponseModes.Add(ResponseModes.Fragment); - - options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken); - options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token); - options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.Token); - }); - - /// - /// Enables implicit flow support. For more information - /// about this specific OAuth 2.0/OpenID Connect flow, visit - /// https://tools.ietf.org/html/rfc6749#section-4.2 and - /// http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth. - /// - /// - /// The implicit flow is not recommended for new applications and should - /// only be enabled when maintaining backward compatibility is important. - /// - /// The . - public OpenIddictServerBuilder AllowImplicitFlow() - => Configure(options => - { - options.GrantTypes.Add(GrantTypes.Implicit); - - options.ResponseModes.Add(ResponseModes.FormPost); - options.ResponseModes.Add(ResponseModes.Fragment); - - options.ResponseTypes.Add(ResponseTypes.IdToken); - options.ResponseTypes.Add(ResponseTypes.IdToken + ' ' + ResponseTypes.Token); - options.ResponseTypes.Add(ResponseTypes.Token); - }); - - /// - /// Enables none flow support. For more information about this specific OAuth 2.0 flow, - /// visit https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#none. - /// - /// The . - public OpenIddictServerBuilder AllowNoneFlow() - => Configure(options => options.ResponseTypes.Add(ResponseTypes.None)); - - /// - /// Enables password flow support. For more information about this specific - /// OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc6749#section-4.3. - /// - /// - /// The password flow is not recommended for new applications and should - /// only be enabled when maintaining backward compatibility is important. - /// - /// The . - public OpenIddictServerBuilder AllowPasswordFlow() - => Configure(options => options.GrantTypes.Add(GrantTypes.Password)); - - /// - /// Enables refresh token flow support. For more information about this - /// specific OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc6749#section-6. - /// - /// The . - public OpenIddictServerBuilder AllowRefreshTokenFlow() - => Configure(options => - { - options.GrantTypes.Add(GrantTypes.RefreshToken); + /// + /// Enables authorization code flow support. For more information + /// about this specific OAuth 2.0/OpenID Connect flow, visit + /// https://tools.ietf.org/html/rfc6749#section-4.1 and + /// http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. + /// + /// The . + public OpenIddictServerBuilder AllowAuthorizationCodeFlow() + => Configure(options => + { + options.CodeChallengeMethods.Add(CodeChallengeMethods.Sha256); + + options.GrantTypes.Add(GrantTypes.AuthorizationCode); - options.Scopes.Add(Scopes.OfflineAccess); - }); + options.ResponseModes.Add(ResponseModes.FormPost); + options.ResponseModes.Add(ResponseModes.Fragment); + options.ResponseModes.Add(ResponseModes.Query); + + options.ResponseTypes.Add(ResponseTypes.Code); + }); + + /// + /// Enables client credentials flow support. For more information about this + /// specific OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc6749#section-4.4. + /// + /// The . + public OpenIddictServerBuilder AllowClientCredentialsFlow() + => Configure(options => options.GrantTypes.Add(GrantTypes.ClientCredentials)); - /// - /// Sets the relative or absolute URLs associated to the authorization endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetAuthorizationEndpointUris(params string[] addresses) + /// + /// Enables custom grant type support. + /// + /// The grant type associated with the flow. + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictServerBuilder AllowCustomFlow(string type) + { + if (string.IsNullOrEmpty(type)) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0071), nameof(type)); + } + + return Configure(options => options.GrantTypes.Add(type)); + } + + /// + /// Enables device code flow support. For more information about this + /// specific OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc8628. + /// + /// The . + public OpenIddictServerBuilder AllowDeviceCodeFlow() + => Configure(options => options.GrantTypes.Add(GrantTypes.DeviceCode)); + + /// + /// Enables hybrid flow support. For more information + /// about this specific OpenID Connect flow, visit + /// http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth. + /// + /// The . + public OpenIddictServerBuilder AllowHybridFlow() + => Configure(options => + { + options.CodeChallengeMethods.Add(CodeChallengeMethods.Sha256); - return SetAuthorizationEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + options.GrantTypes.Add(GrantTypes.AuthorizationCode); + options.GrantTypes.Add(GrantTypes.Implicit); + + options.ResponseModes.Add(ResponseModes.FormPost); + options.ResponseModes.Add(ResponseModes.Fragment); + + options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken); + options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token); + options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.Token); + }); + + /// + /// Enables implicit flow support. For more information + /// about this specific OAuth 2.0/OpenID Connect flow, visit + /// https://tools.ietf.org/html/rfc6749#section-4.2 and + /// http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth. + /// + /// + /// The implicit flow is not recommended for new applications and should + /// only be enabled when maintaining backward compatibility is important. + /// + /// The . + public OpenIddictServerBuilder AllowImplicitFlow() + => Configure(options => + { + options.GrantTypes.Add(GrantTypes.Implicit); + + options.ResponseModes.Add(ResponseModes.FormPost); + options.ResponseModes.Add(ResponseModes.Fragment); + + options.ResponseTypes.Add(ResponseTypes.IdToken); + options.ResponseTypes.Add(ResponseTypes.IdToken + ' ' + ResponseTypes.Token); + options.ResponseTypes.Add(ResponseTypes.Token); + }); + + /// + /// Enables none flow support. For more information about this specific OAuth 2.0 flow, + /// visit https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#none. + /// + /// The . + public OpenIddictServerBuilder AllowNoneFlow() + => Configure(options => options.ResponseTypes.Add(ResponseTypes.None)); + + /// + /// Enables password flow support. For more information about this specific + /// OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc6749#section-4.3. + /// + /// + /// The password flow is not recommended for new applications and should + /// only be enabled when maintaining backward compatibility is important. + /// + /// The . + public OpenIddictServerBuilder AllowPasswordFlow() + => Configure(options => options.GrantTypes.Add(GrantTypes.Password)); + + /// + /// Enables refresh token flow support. For more information about this + /// specific OAuth 2.0 flow, visit https://tools.ietf.org/html/rfc6749#section-6. + /// + /// The . + public OpenIddictServerBuilder AllowRefreshTokenFlow() + => Configure(options => + { + options.GrantTypes.Add(GrantTypes.RefreshToken); + + options.Scopes.Add(Scopes.OfflineAccess); + }); + + /// + /// Sets the relative or absolute URLs associated to the authorization endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetAuthorizationEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the authorization endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetAuthorizationEndpointUris(params Uri[] addresses) + return SetAuthorizationEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } + + /// + /// Sets the relative or absolute URLs associated to the authorization endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetAuthorizationEndpointUris(params Uri[] addresses) + { + if (addresses is null) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentNullException(nameof(addresses)); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + if (addresses.Any(address => !address.IsWellFormedOriginalString())) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); + } - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); + } - return Configure(options => - { - options.AuthorizationEndpointUris.Clear(); - options.AuthorizationEndpointUris.AddRange(addresses); - }); + return Configure(options => + { + options.AuthorizationEndpointUris.Clear(); + options.AuthorizationEndpointUris.AddRange(addresses); + }); + } + + /// + /// Sets the relative or absolute URLs associated to the configuration endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetConfigurationEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the configuration endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetConfigurationEndpointUris(params string[] addresses) + return SetConfigurationEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } + + /// + /// Sets the relative or absolute URLs associated to the configuration endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetConfigurationEndpointUris(params Uri[] addresses) + { + if (addresses is null) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentNullException(nameof(addresses)); + } - return SetConfigurationEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + if (addresses.Any(address => !address.IsWellFormedOriginalString())) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the configuration endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetConfigurationEndpointUris(params Uri[] addresses) + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + return Configure(options => + { + options.ConfigurationEndpointUris.Clear(); + options.ConfigurationEndpointUris.AddRange(addresses); + }); + } - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + /// + /// Sets the relative or absolute URLs associated to the cryptography endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetCryptographyEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); + } - return Configure(options => - { - options.ConfigurationEndpointUris.Clear(); - options.ConfigurationEndpointUris.AddRange(addresses); - }); + return SetCryptographyEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } + + /// + /// Sets the relative or absolute URLs associated to the cryptography endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetCryptographyEndpointUris(params Uri[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the cryptography endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetCryptographyEndpointUris(params string[] addresses) + if (addresses.Any(address => !address.IsWellFormedOriginalString())) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); + } - return SetCryptographyEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the cryptography endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetCryptographyEndpointUris(params Uri[] addresses) + return Configure(options => { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + options.CryptographyEndpointUris.Clear(); + options.CryptographyEndpointUris.AddRange(addresses); + }); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + /// + /// Sets the relative or absolute URLs associated to the device endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetDeviceEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); + } - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + return SetDeviceEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } - return Configure(options => - { - options.CryptographyEndpointUris.Clear(); - options.CryptographyEndpointUris.AddRange(addresses); - }); + /// + /// Sets the relative or absolute URLs associated to the device endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetDeviceEndpointUris(params Uri[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the device endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetDeviceEndpointUris(params string[] addresses) + if (addresses.Any(address => !address.IsWellFormedOriginalString())) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); + } - return SetDeviceEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the device endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetDeviceEndpointUris(params Uri[] addresses) + return Configure(options => { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + options.DeviceEndpointUris.Clear(); + options.DeviceEndpointUris.AddRange(addresses); + }); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + /// + /// Sets the relative or absolute URLs associated to the introspection endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetIntrospectionEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); + } - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + return SetIntrospectionEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } - return Configure(options => - { - options.DeviceEndpointUris.Clear(); - options.DeviceEndpointUris.AddRange(addresses); - }); + /// + /// Sets the relative or absolute URLs associated to the introspection endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetIntrospectionEndpointUris(params Uri[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the introspection endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetIntrospectionEndpointUris(params string[] addresses) + if (addresses.Any(address => !address.IsWellFormedOriginalString())) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); + } - return SetIntrospectionEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the introspection endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetIntrospectionEndpointUris(params Uri[] addresses) + return Configure(options => { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + options.IntrospectionEndpointUris.Clear(); + options.IntrospectionEndpointUris.AddRange(addresses); + }); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + /// + /// Sets the relative or absolute URLs associated to the logout endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetLogoutEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); + } - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + return SetLogoutEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } - return Configure(options => - { - options.IntrospectionEndpointUris.Clear(); - options.IntrospectionEndpointUris.AddRange(addresses); - }); + /// + /// Sets the relative or absolute URLs associated to the logout endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetLogoutEndpointUris(params Uri[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the logout endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetLogoutEndpointUris(params string[] addresses) + if (addresses.Any(address => !address.IsWellFormedOriginalString())) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); + } - return SetLogoutEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the logout endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetLogoutEndpointUris(params Uri[] addresses) + return Configure(options => { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + options.LogoutEndpointUris.Clear(); + options.LogoutEndpointUris.AddRange(addresses); + }); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + /// + /// Sets the relative or absolute URLs associated to the revocation endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetRevocationEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); + } - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + return SetRevocationEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } - return Configure(options => - { - options.LogoutEndpointUris.Clear(); - options.LogoutEndpointUris.AddRange(addresses); - }); + /// + /// Sets the relative or absolute URLs associated to the revocation endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetRevocationEndpointUris(params Uri[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the revocation endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetRevocationEndpointUris(params string[] addresses) + if (addresses.Any(address => !address.IsWellFormedOriginalString())) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); + } - return SetRevocationEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the revocation endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetRevocationEndpointUris(params Uri[] addresses) + return Configure(options => { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + options.RevocationEndpointUris.Clear(); + options.RevocationEndpointUris.AddRange(addresses); + }); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + /// + /// Sets the relative or absolute URLs associated to the token endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetTokenEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); + } - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + return SetTokenEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } - return Configure(options => - { - options.RevocationEndpointUris.Clear(); - options.RevocationEndpointUris.AddRange(addresses); - }); + /// + /// Sets the relative or absolute URLs associated to the token endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetTokenEndpointUris(params Uri[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the token endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetTokenEndpointUris(params string[] addresses) + if (addresses.Any(address => !address.IsWellFormedOriginalString())) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); + } - return SetTokenEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the token endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetTokenEndpointUris(params Uri[] addresses) + return Configure(options => { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + options.TokenEndpointUris.Clear(); + options.TokenEndpointUris.AddRange(addresses); + }); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + /// + /// Sets the relative or absolute URLs associated to the userinfo endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetUserinfoEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); + } - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + return SetUserinfoEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } - return Configure(options => - { - options.TokenEndpointUris.Clear(); - options.TokenEndpointUris.AddRange(addresses); - }); + /// + /// Sets the relative or absolute URLs associated to the userinfo endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned as part of the discovery document. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetUserinfoEndpointUris(params Uri[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the userinfo endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetUserinfoEndpointUris(params string[] addresses) + if (addresses.Any(address => !address.IsWellFormedOriginalString())) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); + } - return SetUserinfoEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the userinfo endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned as part of the discovery document. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetUserinfoEndpointUris(params Uri[] addresses) + return Configure(options => { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + options.UserinfoEndpointUris.Clear(); + options.UserinfoEndpointUris.AddRange(addresses); + }); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + /// + /// Sets the relative or absolute URLs associated to the verification endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned by the device endpoint. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetVerificationEndpointUris(params string[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); + } - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + return SetVerificationEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + } - return Configure(options => - { - options.UserinfoEndpointUris.Clear(); - options.UserinfoEndpointUris.AddRange(addresses); - }); + /// + /// Sets the relative or absolute URLs associated to the verification endpoint. + /// If an empty array is specified, the endpoint will be considered disabled. + /// Note: only the first address will be returned by the device endpoint. + /// + /// The addresses associated to the endpoint. + /// The . + public OpenIddictServerBuilder SetVerificationEndpointUris(params Uri[] addresses) + { + if (addresses is null) + { + throw new ArgumentNullException(nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the verification endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned by the device endpoint. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetVerificationEndpointUris(params string[] addresses) + if (addresses.Any(address => !address.IsWellFormedOriginalString())) { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); + } - return SetVerificationEndpointUris(addresses.Select(address => new Uri(address, UriKind.RelativeOrAbsolute)).ToArray()); + if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); } - /// - /// Sets the relative or absolute URLs associated to the verification endpoint. - /// If an empty array is specified, the endpoint will be considered disabled. - /// Note: only the first address will be returned by the device endpoint. - /// - /// The addresses associated to the endpoint. - /// The . - public OpenIddictServerBuilder SetVerificationEndpointUris(params Uri[] addresses) + return Configure(options => { - if (addresses is null) - { - throw new ArgumentNullException(nameof(addresses)); - } + options.VerificationEndpointUris.Clear(); + options.VerificationEndpointUris.AddRange(addresses); + }); + } - if (addresses.Any(address => !address.IsWellFormedOriginalString())) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0072), nameof(addresses)); - } + /// + /// Disables JWT access token encryption (this option doesn't affect Data Protection tokens). + /// Disabling encryption is NOT recommended and SHOULD only be done when issuing tokens + /// to third-party resource servers/APIs you don't control and don't fully trust. + /// + /// The . + public OpenIddictServerBuilder DisableAccessTokenEncryption() + => Configure(options => options.DisableAccessTokenEncryption = true); - if (addresses.Any(address => address.OriginalString.StartsWith("~", StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(SR.FormatID0081("~"), nameof(addresses)); - } + /// + /// Disables authorization storage so that ad-hoc authorizations are + /// not created when an authorization code or refresh token is issued + /// and can't be revoked to prevent associated tokens from being used. + /// Using this option is generally NOT recommended. + /// + /// The . + public OpenIddictServerBuilder DisableAuthorizationStorage() + => Configure(options => options.DisableAuthorizationStorage = true); - return Configure(options => - { - options.VerificationEndpointUris.Clear(); - options.VerificationEndpointUris.AddRange(addresses); - }); - } - - /// - /// Disables JWT access token encryption (this option doesn't affect Data Protection tokens). - /// Disabling encryption is NOT recommended and SHOULD only be done when issuing tokens - /// to third-party resource servers/APIs you don't control and don't fully trust. - /// - /// The . - public OpenIddictServerBuilder DisableAccessTokenEncryption() - => Configure(options => options.DisableAccessTokenEncryption = true); - - /// - /// Disables authorization storage so that ad-hoc authorizations are - /// not created when an authorization code or refresh token is issued - /// and can't be revoked to prevent associated tokens from being used. - /// Using this option is generally NOT recommended. - /// - /// The . - public OpenIddictServerBuilder DisableAuthorizationStorage() - => Configure(options => options.DisableAuthorizationStorage = true); - - /// - /// Configures OpenIddict to disable rolling refresh tokens so - /// that refresh tokens used in a token request are not marked - /// as redeemed and can still be used until they expire. Disabling - /// rolling refresh tokens is NOT recommended, for security reasons. - /// - /// The . - public OpenIddictServerBuilder DisableRollingRefreshTokens() - => Configure(options => options.DisableRollingRefreshTokens = true); - - /// - /// Allows processing authorization and token requests that specify scopes that have not - /// been registered using or the scope manager. - /// - /// The . - public OpenIddictServerBuilder DisableScopeValidation() - => Configure(options => options.DisableScopeValidation = true); - - /// - /// Disables sliding expiration. When using this option, refresh tokens - /// are issued with a fixed expiration date: when they expire, a complete - /// authorization flow must be started to retrieve a new refresh token. - /// - /// The . - public OpenIddictServerBuilder DisableSlidingRefreshTokenExpiration() - => Configure(options => options.DisableSlidingRefreshTokenExpiration = true); - - /// - /// Disables token storage, so that no database entry is created - /// for the tokens and codes returned by the OpenIddict server. - /// Using this option is generally NOT recommended as it prevents - /// the tokens and codes from being revoked (if needed). - /// Note: disabling token storage requires disabling sliding - /// expiration or enabling rolling tokens. - /// - /// The . - public OpenIddictServerBuilder DisableTokenStorage() - => Configure(options => options.DisableTokenStorage = true); - - /// - /// Enables the degraded mode. When the degraded mode is enabled, all the security checks that - /// depend on the OpenIddict core managers are disabled. This option MUST be enabled with extreme - /// caution and custom handlers MUST be registered to properly validate OpenID Connect requests. - /// - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerBuilder EnableDegradedMode() - => Configure(options => options.EnableDegradedMode = true); - - /// - /// Disables endpoint permissions enforcement. Calling this method is NOT recommended. - /// - /// The . - public OpenIddictServerBuilder IgnoreEndpointPermissions() - => Configure(options => options.IgnoreEndpointPermissions = true); - - /// - /// Disables grant type permissions enforcement. Calling this method is NOT recommended. - /// - /// The . - public OpenIddictServerBuilder IgnoreGrantTypePermissions() - => Configure(options => options.IgnoreGrantTypePermissions = true); - - /// - /// Disables response type permissions enforcement. Calling this method is NOT recommended. - /// - /// The . - public OpenIddictServerBuilder IgnoreResponseTypePermissions() - => Configure(options => options.IgnoreResponseTypePermissions = true); - - /// - /// Disables scope permissions enforcement. Calling this method is NOT recommended. - /// - /// The . - public OpenIddictServerBuilder IgnoreScopePermissions() - => Configure(options => options.IgnoreScopePermissions = true); - - /// - /// Registers the specified claims as supported claims so - /// they can be returned as part of the discovery document. - /// - /// The supported claims. - /// The . - public OpenIddictServerBuilder RegisterClaims(params string[] claims) - { - if (claims is null) - { - throw new ArgumentNullException(nameof(claims)); - } + /// + /// Configures OpenIddict to disable rolling refresh tokens so + /// that refresh tokens used in a token request are not marked + /// as redeemed and can still be used until they expire. Disabling + /// rolling refresh tokens is NOT recommended, for security reasons. + /// + /// The . + public OpenIddictServerBuilder DisableRollingRefreshTokens() + => Configure(options => options.DisableRollingRefreshTokens = true); - if (claims.Any(claim => string.IsNullOrEmpty(claim))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0073), nameof(claims)); - } + /// + /// Allows processing authorization and token requests that specify scopes that have not + /// been registered using or the scope manager. + /// + /// The . + public OpenIddictServerBuilder DisableScopeValidation() + => Configure(options => options.DisableScopeValidation = true); + + /// + /// Disables sliding expiration. When using this option, refresh tokens + /// are issued with a fixed expiration date: when they expire, a complete + /// authorization flow must be started to retrieve a new refresh token. + /// + /// The . + public OpenIddictServerBuilder DisableSlidingRefreshTokenExpiration() + => Configure(options => options.DisableSlidingRefreshTokenExpiration = true); + + /// + /// Disables token storage, so that no database entry is created + /// for the tokens and codes returned by the OpenIddict server. + /// Using this option is generally NOT recommended as it prevents + /// the tokens and codes from being revoked (if needed). + /// Note: disabling token storage requires disabling sliding + /// expiration or enabling rolling tokens. + /// + /// The . + public OpenIddictServerBuilder DisableTokenStorage() + => Configure(options => options.DisableTokenStorage = true); + + /// + /// Enables the degraded mode. When the degraded mode is enabled, all the security checks that + /// depend on the OpenIddict core managers are disabled. This option MUST be enabled with extreme + /// caution and custom handlers MUST be registered to properly validate OpenID Connect requests. + /// + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictServerBuilder EnableDegradedMode() + => Configure(options => options.EnableDegradedMode = true); + + /// + /// Disables endpoint permissions enforcement. Calling this method is NOT recommended. + /// + /// The . + public OpenIddictServerBuilder IgnoreEndpointPermissions() + => Configure(options => options.IgnoreEndpointPermissions = true); + + /// + /// Disables grant type permissions enforcement. Calling this method is NOT recommended. + /// + /// The . + public OpenIddictServerBuilder IgnoreGrantTypePermissions() + => Configure(options => options.IgnoreGrantTypePermissions = true); + + /// + /// Disables response type permissions enforcement. Calling this method is NOT recommended. + /// + /// The . + public OpenIddictServerBuilder IgnoreResponseTypePermissions() + => Configure(options => options.IgnoreResponseTypePermissions = true); + + /// + /// Disables scope permissions enforcement. Calling this method is NOT recommended. + /// + /// The . + public OpenIddictServerBuilder IgnoreScopePermissions() + => Configure(options => options.IgnoreScopePermissions = true); - return Configure(options => options.Claims.UnionWith(claims)); + /// + /// Registers the specified claims as supported claims so + /// they can be returned as part of the discovery document. + /// + /// The supported claims. + /// The . + public OpenIddictServerBuilder RegisterClaims(params string[] claims) + { + if (claims is null) + { + throw new ArgumentNullException(nameof(claims)); } - /// - /// Registers the specified scopes as supported scopes so - /// they can be returned as part of the discovery document. - /// - /// The supported scopes. - /// The . - public OpenIddictServerBuilder RegisterScopes(params string[] scopes) + if (claims.Any(claim => string.IsNullOrEmpty(claim))) { - if (scopes is null) - { - throw new ArgumentNullException(nameof(scopes)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0073), nameof(claims)); + } - if (scopes.Any(scope => string.IsNullOrEmpty(scope))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0074), nameof(scopes)); - } + return Configure(options => options.Claims.UnionWith(claims)); + } - return Configure(options => options.Scopes.UnionWith(scopes)); - } - - /// - /// Configures OpenIddict to force client applications to use Proof Key for Code Exchange - /// (PKCE) when requesting an authorization code (e.g when using the code or hybrid flows). - /// When enforced, authorization requests that lack the code_challenge will be rejected. - /// - /// The . - public OpenIddictServerBuilder RequireProofKeyForCodeExchange() - => Configure(options => options.RequireProofKeyForCodeExchange = true); - - /// - /// Sets the access token lifetime, after which client applications must retrieve - /// a new access token by making a grant_type=refresh_token token request - /// or a prompt=none authorization request, depending on the selected flow. - /// Using long-lived access tokens or tokens that never expire is not recommended. - /// While discouraged, null can be specified to issue tokens that never expire. - /// - /// The access token lifetime. - /// The . - public OpenIddictServerBuilder SetAccessTokenLifetime(TimeSpan? lifetime) - => Configure(options => options.AccessTokenLifetime = lifetime); - - /// - /// Sets the authorization code lifetime, after which client applications - /// are unable to send a grant_type=authorization_code token request. - /// Using short-lived authorization codes is strongly recommended. - /// While discouraged, null can be specified to issue codes that never expire. - /// - /// The authorization code lifetime. - /// The . - public OpenIddictServerBuilder SetAuthorizationCodeLifetime(TimeSpan? lifetime) - => Configure(options => options.AuthorizationCodeLifetime = lifetime); - - /// - /// Sets the device code lifetime, after which client applications are unable to - /// send a grant_type=urn:ietf:params:oauth:grant-type:device_code token request. - /// Using short-lived device codes is strongly recommended. - /// While discouraged, null can be specified to issue codes that never expire. - /// - /// The authorization code lifetime. - /// The . - public OpenIddictServerBuilder SetDeviceCodeLifetime(TimeSpan? lifetime) - => Configure(options => options.DeviceCodeLifetime = lifetime); - - /// - /// Sets the identity token lifetime, after which client - /// applications should refuse processing identity tokens. - /// While discouraged, null can be specified to issue tokens that never expire. - /// - /// The identity token lifetime. - /// The . - public OpenIddictServerBuilder SetIdentityTokenLifetime(TimeSpan? lifetime) - => Configure(options => options.IdentityTokenLifetime = lifetime); - - /// - /// Sets the refresh token lifetime, after which client applications must get - /// a new authorization from the user. When sliding expiration is enabled, - /// a new refresh token is always issued to the client application, - /// which prolongs the validity period of the refresh token. - /// While discouraged, null can be specified to issue tokens that never expire. - /// - /// The refresh token lifetime. - /// The . - public OpenIddictServerBuilder SetRefreshTokenLifetime(TimeSpan? lifetime) - => Configure(options => options.RefreshTokenLifetime = lifetime); - - /// - /// Sets the refresh token reuse leeway, during which rolling refresh tokens marked - /// as redeemed can still be used to make concurrent refresh token requests. - /// - /// The refresh token reuse interval. - /// The . - public OpenIddictServerBuilder SetRefreshTokenReuseLeeway(TimeSpan? leeway) - => Configure(options => options.RefreshTokenReuseLeeway = leeway); - - /// - /// Sets the user code lifetime, after which they'll no longer be considered valid. - /// Using short-lived device codes is strongly recommended. - /// While discouraged, null can be specified to issue codes that never expire. - /// - /// The authorization code lifetime. - /// The . - public OpenIddictServerBuilder SetUserCodeLifetime(TimeSpan? lifetime) - => Configure(options => options.UserCodeLifetime = lifetime); - - /// - /// Sets the issuer address, which is used as the base address - /// for the endpoint URIs returned from the discovery endpoint. - /// - /// The issuer address. - /// The . - public OpenIddictServerBuilder SetIssuer(Uri address) - { - if (address is null) - { - throw new ArgumentNullException(nameof(address)); - } + /// + /// Registers the specified scopes as supported scopes so + /// they can be returned as part of the discovery document. + /// + /// The supported scopes. + /// The . + public OpenIddictServerBuilder RegisterScopes(params string[] scopes) + { + if (scopes is null) + { + throw new ArgumentNullException(nameof(scopes)); + } + + if (scopes.Any(scope => string.IsNullOrEmpty(scope))) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0074), nameof(scopes)); + } - return Configure(options => options.Issuer = address); - } - - /// - /// Configures OpenIddict to use reference tokens, so that the access token payloads - /// are stored in the database (only an identifier is returned to the client application). - /// Enabling this option is useful when storing a very large number of claims in the tokens, - /// but it is RECOMMENDED to enable column encryption in the database or use the ASP.NET Core - /// Data Protection integration, that provides additional protection against token leakage. - /// - /// The . - public OpenIddictServerBuilder UseReferenceAccessTokens() - => Configure(options => options.UseReferenceAccessTokens = true); - - /// - /// Configures OpenIddict to use reference tokens, so that the refresh token payloads - /// are stored in the database (only an identifier is returned to the client application). - /// Enabling this option is useful when storing a very large number of claims in the tokens, - /// but it is RECOMMENDED to enable column encryption in the database or use the ASP.NET Core - /// Data Protection integration, that provides additional protection against token leakage. - /// - /// The . - public OpenIddictServerBuilder UseReferenceRefreshTokens() - => Configure(options => options.UseReferenceRefreshTokens = true); - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); + return Configure(options => options.Scopes.UnionWith(scopes)); } + + /// + /// Configures OpenIddict to force client applications to use Proof Key for Code Exchange + /// (PKCE) when requesting an authorization code (e.g when using the code or hybrid flows). + /// When enforced, authorization requests that lack the code_challenge will be rejected. + /// + /// The . + public OpenIddictServerBuilder RequireProofKeyForCodeExchange() + => Configure(options => options.RequireProofKeyForCodeExchange = true); + + /// + /// Sets the access token lifetime, after which client applications must retrieve + /// a new access token by making a grant_type=refresh_token token request + /// or a prompt=none authorization request, depending on the selected flow. + /// Using long-lived access tokens or tokens that never expire is not recommended. + /// While discouraged, null can be specified to issue tokens that never expire. + /// + /// The access token lifetime. + /// The . + public OpenIddictServerBuilder SetAccessTokenLifetime(TimeSpan? lifetime) + => Configure(options => options.AccessTokenLifetime = lifetime); + + /// + /// Sets the authorization code lifetime, after which client applications + /// are unable to send a grant_type=authorization_code token request. + /// Using short-lived authorization codes is strongly recommended. + /// While discouraged, null can be specified to issue codes that never expire. + /// + /// The authorization code lifetime. + /// The . + public OpenIddictServerBuilder SetAuthorizationCodeLifetime(TimeSpan? lifetime) + => Configure(options => options.AuthorizationCodeLifetime = lifetime); + + /// + /// Sets the device code lifetime, after which client applications are unable to + /// send a grant_type=urn:ietf:params:oauth:grant-type:device_code token request. + /// Using short-lived device codes is strongly recommended. + /// While discouraged, null can be specified to issue codes that never expire. + /// + /// The authorization code lifetime. + /// The . + public OpenIddictServerBuilder SetDeviceCodeLifetime(TimeSpan? lifetime) + => Configure(options => options.DeviceCodeLifetime = lifetime); + + /// + /// Sets the identity token lifetime, after which client + /// applications should refuse processing identity tokens. + /// While discouraged, null can be specified to issue tokens that never expire. + /// + /// The identity token lifetime. + /// The . + public OpenIddictServerBuilder SetIdentityTokenLifetime(TimeSpan? lifetime) + => Configure(options => options.IdentityTokenLifetime = lifetime); + + /// + /// Sets the refresh token lifetime, after which client applications must get + /// a new authorization from the user. When sliding expiration is enabled, + /// a new refresh token is always issued to the client application, + /// which prolongs the validity period of the refresh token. + /// While discouraged, null can be specified to issue tokens that never expire. + /// + /// The refresh token lifetime. + /// The . + public OpenIddictServerBuilder SetRefreshTokenLifetime(TimeSpan? lifetime) + => Configure(options => options.RefreshTokenLifetime = lifetime); + + /// + /// Sets the refresh token reuse leeway, during which rolling refresh tokens marked + /// as redeemed can still be used to make concurrent refresh token requests. + /// + /// The refresh token reuse interval. + /// The . + public OpenIddictServerBuilder SetRefreshTokenReuseLeeway(TimeSpan? leeway) + => Configure(options => options.RefreshTokenReuseLeeway = leeway); + + /// + /// Sets the user code lifetime, after which they'll no longer be considered valid. + /// Using short-lived device codes is strongly recommended. + /// While discouraged, null can be specified to issue codes that never expire. + /// + /// The authorization code lifetime. + /// The . + public OpenIddictServerBuilder SetUserCodeLifetime(TimeSpan? lifetime) + => Configure(options => options.UserCodeLifetime = lifetime); + + /// + /// Sets the issuer address, which is used as the base address + /// for the endpoint URIs returned from the discovery endpoint. + /// + /// The issuer address. + /// The . + public OpenIddictServerBuilder SetIssuer(Uri address) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } + + return Configure(options => options.Issuer = address); + } + + /// + /// Configures OpenIddict to use reference tokens, so that the access token payloads + /// are stored in the database (only an identifier is returned to the client application). + /// Enabling this option is useful when storing a very large number of claims in the tokens, + /// but it is RECOMMENDED to enable column encryption in the database or use the ASP.NET Core + /// Data Protection integration, that provides additional protection against token leakage. + /// + /// The . + public OpenIddictServerBuilder UseReferenceAccessTokens() + => Configure(options => options.UseReferenceAccessTokens = true); + + /// + /// Configures OpenIddict to use reference tokens, so that the refresh token payloads + /// are stored in the database (only an identifier is returned to the client application). + /// Enabling this option is useful when storing a very large number of claims in the tokens, + /// but it is RECOMMENDED to enable column encryption in the database or use the ASP.NET Core + /// Data Protection integration, that provides additional protection against token leakage. + /// + /// The . + public OpenIddictServerBuilder UseReferenceRefreshTokens() + => Configure(options => options.UseReferenceRefreshTokens = true); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs index 9329c19b..bcfbd87e 100644 --- a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs +++ b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs @@ -15,335 +15,334 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +/// +/// Contains the methods required to ensure that the OpenIddict server configuration is valid. +/// +public class OpenIddictServerConfiguration : IPostConfigureOptions { /// - /// Contains the methods required to ensure that the OpenIddict server configuration is valid. + /// Populates the default OpenIddict server options and ensures + /// that the configuration is in a consistent and valid state. /// - public class OpenIddictServerConfiguration : IPostConfigureOptions + /// The name of the options instance to configure, if applicable. + /// The options instance to initialize. + public void PostConfigure(string name, OpenIddictServerOptions options) { - /// - /// Populates the default OpenIddict server options and ensures - /// that the configuration is in a consistent and valid state. - /// - /// The name of the options instance to configure, if applicable. - /// The options instance to initialize. - public void PostConfigure(string name, OpenIddictServerOptions options) + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new ArgumentNullException(nameof(options)); + } - // Explicitly disable all the features that are implicitly excluded when the degraded mode is active. - if (options.EnableDegradedMode) - { - options.DisableAuthorizationStorage = options.DisableTokenStorage = options.DisableRollingRefreshTokens = true; - options.IgnoreEndpointPermissions = options.IgnoreGrantTypePermissions = true; - options.IgnoreResponseTypePermissions = options.IgnoreScopePermissions = true; - options.UseReferenceAccessTokens = options.UseReferenceRefreshTokens = false; - } + // Explicitly disable all the features that are implicitly excluded when the degraded mode is active. + if (options.EnableDegradedMode) + { + options.DisableAuthorizationStorage = options.DisableTokenStorage = options.DisableRollingRefreshTokens = true; + options.IgnoreEndpointPermissions = options.IgnoreGrantTypePermissions = true; + options.IgnoreResponseTypePermissions = options.IgnoreScopePermissions = true; + options.UseReferenceAccessTokens = options.UseReferenceRefreshTokens = false; + } - // Explicitly disable rolling refresh tokens when token storage is disabled. - if (options.DisableTokenStorage) - { - options.DisableRollingRefreshTokens = true; - } + // Explicitly disable rolling refresh tokens when token storage is disabled. + if (options.DisableTokenStorage) + { + options.DisableRollingRefreshTokens = true; + } - if (options.JsonWebTokenHandler is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0075)); - } + if (options.JsonWebTokenHandler is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0075)); + } - // Ensure at least one flow has been enabled. - if (options.GrantTypes.Count == 0) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0076)); - } + // Ensure at least one flow has been enabled. + if (options.GrantTypes.Count == 0) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0076)); + } - var addresses = options.AuthorizationEndpointUris.Distinct() - .Concat(options.ConfigurationEndpointUris.Distinct()) - .Concat(options.CryptographyEndpointUris.Distinct()) - .Concat(options.DeviceEndpointUris.Distinct()) - .Concat(options.IntrospectionEndpointUris.Distinct()) - .Concat(options.LogoutEndpointUris.Distinct()) - .Concat(options.RevocationEndpointUris.Distinct()) - .Concat(options.TokenEndpointUris.Distinct()) - .Concat(options.UserinfoEndpointUris.Distinct()) - .Concat(options.VerificationEndpointUris.Distinct()) - .ToList(); - - // Ensure endpoint addresses are unique across endpoints. - if (addresses.Count != addresses.Distinct().Count()) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0285)); - } + var addresses = options.AuthorizationEndpointUris.Distinct() + .Concat(options.ConfigurationEndpointUris.Distinct()) + .Concat(options.CryptographyEndpointUris.Distinct()) + .Concat(options.DeviceEndpointUris.Distinct()) + .Concat(options.IntrospectionEndpointUris.Distinct()) + .Concat(options.LogoutEndpointUris.Distinct()) + .Concat(options.RevocationEndpointUris.Distinct()) + .Concat(options.TokenEndpointUris.Distinct()) + .Concat(options.UserinfoEndpointUris.Distinct()) + .Concat(options.VerificationEndpointUris.Distinct()) + .ToList(); + + // Ensure endpoint addresses are unique across endpoints. + if (addresses.Count != addresses.Distinct().Count()) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0285)); + } - // Ensure the authorization endpoint has been enabled when - // the authorization code or implicit grants are supported. - if (options.AuthorizationEndpointUris.Count == 0 && (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) || - options.GrantTypes.Contains(GrantTypes.Implicit))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0077)); - } + // Ensure the authorization endpoint has been enabled when + // the authorization code or implicit grants are supported. + if (options.AuthorizationEndpointUris.Count == 0 && (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) || + options.GrantTypes.Contains(GrantTypes.Implicit))) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0077)); + } - // Ensure the device endpoint has been enabled when the device grant is supported. - if (options.DeviceEndpointUris.Count == 0 && options.GrantTypes.Contains(GrantTypes.DeviceCode)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0078)); - } + // Ensure the device endpoint has been enabled when the device grant is supported. + if (options.DeviceEndpointUris.Count == 0 && options.GrantTypes.Contains(GrantTypes.DeviceCode)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0078)); + } + + // Ensure the token endpoint has been enabled when the authorization code, + // client credentials, device, password or refresh token grants are supported. + if (options.TokenEndpointUris.Count == 0 && (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) || + options.GrantTypes.Contains(GrantTypes.ClientCredentials) || + options.GrantTypes.Contains(GrantTypes.DeviceCode) || + options.GrantTypes.Contains(GrantTypes.Password) || + options.GrantTypes.Contains(GrantTypes.RefreshToken))) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0079)); + } - // Ensure the token endpoint has been enabled when the authorization code, - // client credentials, device, password or refresh token grants are supported. - if (options.TokenEndpointUris.Count == 0 && (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) || - options.GrantTypes.Contains(GrantTypes.ClientCredentials) || - options.GrantTypes.Contains(GrantTypes.DeviceCode) || - options.GrantTypes.Contains(GrantTypes.Password) || - options.GrantTypes.Contains(GrantTypes.RefreshToken))) + // Ensure the verification endpoint has been enabled when the device grant is supported. + if (options.VerificationEndpointUris.Count == 0 && options.GrantTypes.Contains(GrantTypes.DeviceCode)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0080)); + } + + // Ensure the device grant is allowed when the device endpoint is enabled. + if (options.DeviceEndpointUris.Count > 0 && !options.GrantTypes.Contains(GrantTypes.DeviceCode)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0084)); + } + + // Ensure the grant types/response types configuration is consistent. + foreach (var type in options.ResponseTypes) + { + var types = new HashSet(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries), StringComparer.Ordinal); + if (types.Contains(ResponseTypes.Code) && !options.GrantTypes.Contains(GrantTypes.AuthorizationCode)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0079)); + throw new InvalidOperationException(SR.FormatID0281(ResponseTypes.Code)); } - // Ensure the verification endpoint has been enabled when the device grant is supported. - if (options.VerificationEndpointUris.Count == 0 && options.GrantTypes.Contains(GrantTypes.DeviceCode)) + if (types.Contains(ResponseTypes.IdToken) && !options.GrantTypes.Contains(GrantTypes.Implicit)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0080)); + throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.IdToken)); } - // Ensure the device grant is allowed when the device endpoint is enabled. - if (options.DeviceEndpointUris.Count > 0 && !options.GrantTypes.Contains(GrantTypes.DeviceCode)) + if (types.Contains(ResponseTypes.Token) && !options.GrantTypes.Contains(GrantTypes.Implicit)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0084)); + throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.Token)); } + } - // Ensure the grant types/response types configuration is consistent. - foreach (var type in options.ResponseTypes) - { - var types = new HashSet(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries), StringComparer.Ordinal); - if (types.Contains(ResponseTypes.Code) && !options.GrantTypes.Contains(GrantTypes.AuthorizationCode)) - { - throw new InvalidOperationException(SR.FormatID0281(ResponseTypes.Code)); - } + // Ensure reference tokens support was not enabled when token storage is disabled. + if (options.DisableTokenStorage && (options.UseReferenceAccessTokens || options.UseReferenceRefreshTokens)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0083)); + } - if (types.Contains(ResponseTypes.IdToken) && !options.GrantTypes.Contains(GrantTypes.Implicit)) - { - throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.IdToken)); - } + if (options.EncryptionCredentials.Count == 0) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0085)); + } - if (types.Contains(ResponseTypes.Token) && !options.GrantTypes.Contains(GrantTypes.Implicit)) - { - throw new InvalidOperationException(SR.FormatID0282(ResponseTypes.Token)); - } - } + if (!options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0086)); + } + + // If all the registered encryption credentials are backed by a X.509 certificate, at least one of them must be valid. + if (options.EncryptionCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey && + (x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now))) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0087)); + } + + // If all the registered signing credentials are backed by a X.509 certificate, at least one of them must be valid. + if (options.SigningCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey && + (x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now))) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0088)); + } + + if (options.EnableDegradedMode) + { + // If the degraded mode was enabled, ensure custom validation handlers + // have been registered for the endpoints that require manual validation. - // Ensure reference tokens support was not enabled when token storage is disabled. - if (options.DisableTokenStorage && (options.UseReferenceAccessTokens || options.UseReferenceRefreshTokens)) + if (options.AuthorizationEndpointUris.Count != 0 && !options.Handlers.Any( + descriptor => descriptor.ContextType == typeof(ValidateAuthorizationRequestContext) && + descriptor.Type == OpenIddictServerHandlerType.Custom && + descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0083)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0089)); } - if (options.EncryptionCredentials.Count == 0) + if (options.DeviceEndpointUris.Count != 0 && !options.Handlers.Any( + descriptor => descriptor.ContextType == typeof(ValidateDeviceRequestContext) && + descriptor.Type == OpenIddictServerHandlerType.Custom && + descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0085)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0090)); } - if (!options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey)) + if (options.IntrospectionEndpointUris.Count != 0 && !options.Handlers.Any( + descriptor => descriptor.ContextType == typeof(ValidateIntrospectionRequestContext) && + descriptor.Type == OpenIddictServerHandlerType.Custom && + descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0086)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0091)); } - // If all the registered encryption credentials are backed by a X.509 certificate, at least one of them must be valid. - if (options.EncryptionCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey && - (x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now))) + if (options.LogoutEndpointUris.Count != 0 && !options.Handlers.Any( + descriptor => descriptor.ContextType == typeof(ValidateLogoutRequestContext) && + descriptor.Type == OpenIddictServerHandlerType.Custom && + descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0087)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0092)); } - // If all the registered signing credentials are backed by a X.509 certificate, at least one of them must be valid. - if (options.SigningCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey && - (x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now))) + if (options.RevocationEndpointUris.Count != 0 && !options.Handlers.Any( + descriptor => descriptor.ContextType == typeof(ValidateRevocationRequestContext) && + descriptor.Type == OpenIddictServerHandlerType.Custom && + descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0088)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0093)); } - if (options.EnableDegradedMode) + if (options.TokenEndpointUris.Count != 0 && !options.Handlers.Any( + descriptor => descriptor.ContextType == typeof(ValidateTokenRequestContext) && + descriptor.Type == OpenIddictServerHandlerType.Custom && + descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) { - // If the degraded mode was enabled, ensure custom validation handlers - // have been registered for the endpoints that require manual validation. - - if (options.AuthorizationEndpointUris.Count != 0 && !options.Handlers.Any( - descriptor => descriptor.ContextType == typeof(ValidateAuthorizationRequestContext) && - descriptor.Type == OpenIddictServerHandlerType.Custom && - descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0089)); - } - - if (options.DeviceEndpointUris.Count != 0 && !options.Handlers.Any( - descriptor => descriptor.ContextType == typeof(ValidateDeviceRequestContext) && - descriptor.Type == OpenIddictServerHandlerType.Custom && - descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0090)); - } - - if (options.IntrospectionEndpointUris.Count != 0 && !options.Handlers.Any( - descriptor => descriptor.ContextType == typeof(ValidateIntrospectionRequestContext) && - descriptor.Type == OpenIddictServerHandlerType.Custom && - descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0091)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0094)); + } - if (options.LogoutEndpointUris.Count != 0 && !options.Handlers.Any( - descriptor => descriptor.ContextType == typeof(ValidateLogoutRequestContext) && - descriptor.Type == OpenIddictServerHandlerType.Custom && - descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0092)); - } + if (options.VerificationEndpointUris.Count != 0 && !options.Handlers.Any( + descriptor => descriptor.ContextType == typeof(ValidateVerificationRequestContext) && + descriptor.Type == OpenIddictServerHandlerType.Custom && + descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0095)); + } - if (options.RevocationEndpointUris.Count != 0 && !options.Handlers.Any( - descriptor => descriptor.ContextType == typeof(ValidateRevocationRequestContext) && - descriptor.Type == OpenIddictServerHandlerType.Custom && - descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0093)); - } + // If the degraded mode was enabled, ensure custom validation/generation handlers + // have been registered to deal with device/user codes validation and generation. - if (options.TokenEndpointUris.Count != 0 && !options.Handlers.Any( - descriptor => descriptor.ContextType == typeof(ValidateTokenRequestContext) && + if (options.GrantTypes.Contains(GrantTypes.DeviceCode)) + { + if (!options.Handlers.Any( + descriptor => descriptor.ContextType == typeof(ValidateTokenContext) && descriptor.Type == OpenIddictServerHandlerType.Custom && descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0094)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0096)); } - if (options.VerificationEndpointUris.Count != 0 && !options.Handlers.Any( - descriptor => descriptor.ContextType == typeof(ValidateVerificationRequestContext) && + if (!options.Handlers.Any( + descriptor => descriptor.ContextType == typeof(GenerateTokenContext) && descriptor.Type == OpenIddictServerHandlerType.Custom && descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0095)); - } - - // If the degraded mode was enabled, ensure custom validation/generation handlers - // have been registered to deal with device/user codes validation and generation. - - if (options.GrantTypes.Contains(GrantTypes.DeviceCode)) - { - if (!options.Handlers.Any( - descriptor => descriptor.ContextType == typeof(ValidateTokenContext) && - descriptor.Type == OpenIddictServerHandlerType.Custom && - descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0096)); - } - - if (!options.Handlers.Any( - descriptor => descriptor.ContextType == typeof(GenerateTokenContext) && - descriptor.Type == OpenIddictServerHandlerType.Custom && - descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type)))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0097)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0097)); } } + } - // Sort the handlers collection using the order associated with each handler. - options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order)); + // Sort the handlers collection using the order associated with each handler. + options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order)); - // Sort the encryption and signing credentials. - options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key)); - options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key)); + // Sort the encryption and signing credentials. + options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key)); + options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key)); - // Generate a key identifier for the encryption/signing keys that don't already have one. - foreach (var key in options.EncryptionCredentials.Select(credentials => credentials.Key) - .Concat(options.SigningCredentials.Select(credentials => credentials.Key)) - .Where(key => string.IsNullOrEmpty(key.KeyId))) - { - key.KeyId = GetKeyIdentifier(key); - } + // Generate a key identifier for the encryption/signing keys that don't already have one. + foreach (var key in options.EncryptionCredentials.Select(credentials => credentials.Key) + .Concat(options.SigningCredentials.Select(credentials => credentials.Key)) + .Where(key => string.IsNullOrEmpty(key.KeyId))) + { + key.KeyId = GetKeyIdentifier(key); + } - // Attach the signing credentials to the token validation parameters. - options.TokenValidationParameters.IssuerSigningKeys = - from credentials in options.SigningCredentials - select credentials.Key; + // Attach the signing credentials to the token validation parameters. + options.TokenValidationParameters.IssuerSigningKeys = + from credentials in options.SigningCredentials + select credentials.Key; - // Attach the encryption credentials to the token validation parameters. - options.TokenValidationParameters.TokenDecryptionKeys = - from credentials in options.EncryptionCredentials - select credentials.Key; + // Attach the encryption credentials to the token validation parameters. + options.TokenValidationParameters.TokenDecryptionKeys = + from credentials in options.EncryptionCredentials + select credentials.Key; - static int Compare(SecurityKey left, SecurityKey right) => (left, right) switch - { - // If the two keys refer to the same instances, return 0. - (SecurityKey first, SecurityKey second) when ReferenceEquals(first, second) => 0, + static int Compare(SecurityKey left, SecurityKey right) => (left, right) switch + { + // If the two keys refer to the same instances, return 0. + (SecurityKey first, SecurityKey second) when ReferenceEquals(first, second) => 0, + + // If one of the keys is a symmetric key, prefer it to the other one. + (SymmetricSecurityKey, SymmetricSecurityKey) => 0, + (SymmetricSecurityKey, SecurityKey) => -1, + (SecurityKey, SymmetricSecurityKey) => 1, - // If one of the keys is a symmetric key, prefer it to the other one. - (SymmetricSecurityKey, SymmetricSecurityKey) => 0, - (SymmetricSecurityKey, SecurityKey) => -1, - (SecurityKey, SymmetricSecurityKey) => 1, + // If one of the keys is backed by a X.509 certificate, don't prefer it if it's not valid yet. + (X509SecurityKey first, SecurityKey) when first.Certificate.NotBefore > DateTime.Now => 1, + (SecurityKey, X509SecurityKey second) when second.Certificate.NotBefore > DateTime.Now => 1, - // If one of the keys is backed by a X.509 certificate, don't prefer it if it's not valid yet. - (X509SecurityKey first, SecurityKey) when first.Certificate.NotBefore > DateTime.Now => 1, - (SecurityKey, X509SecurityKey second) when second.Certificate.NotBefore > DateTime.Now => 1, + // If the two keys are backed by a X.509 certificate, prefer the one with the furthest expiration date. + (X509SecurityKey first, X509SecurityKey second) => -first.Certificate.NotAfter.CompareTo(second.Certificate.NotAfter), - // If the two keys are backed by a X.509 certificate, prefer the one with the furthest expiration date. - (X509SecurityKey first, X509SecurityKey second) => -first.Certificate.NotAfter.CompareTo(second.Certificate.NotAfter), + // If one of the keys is backed by a X.509 certificate, prefer the X.509 security key. + (X509SecurityKey, SecurityKey) => -1, + (SecurityKey, X509SecurityKey) => 1, - // If one of the keys is backed by a X.509 certificate, prefer the X.509 security key. - (X509SecurityKey, SecurityKey) => -1, - (SecurityKey, X509SecurityKey) => 1, + // If the two keys are not backed by a X.509 certificate, none should be preferred to the other. + (SecurityKey, SecurityKey) => 0 + }; - // If the two keys are not backed by a X.509 certificate, none should be preferred to the other. - (SecurityKey, SecurityKey) => 0 - }; + static string? GetKeyIdentifier(SecurityKey key) + { + // When no key identifier can be retrieved from the security keys, a value is automatically + // inferred from the hexadecimal representation of the certificate thumbprint (SHA-1) + // when the key is bound to a X.509 certificate or from the public part of the signing key. - static string? GetKeyIdentifier(SecurityKey key) + if (key is X509SecurityKey x509SecurityKey) { - // When no key identifier can be retrieved from the security keys, a value is automatically - // inferred from the hexadecimal representation of the certificate thumbprint (SHA-1) - // when the key is bound to a X.509 certificate or from the public part of the signing key. + return x509SecurityKey.Certificate.Thumbprint; + } - if (key is X509SecurityKey x509SecurityKey) + if (key is RsaSecurityKey rsaSecurityKey) + { + // Note: if the RSA parameters are not attached to the signing key, + // extract them by calling ExportParameters on the RSA instance. + var parameters = rsaSecurityKey.Parameters; + if (parameters.Modulus is null) { - return x509SecurityKey.Certificate.Thumbprint; - } + parameters = rsaSecurityKey.Rsa.ExportParameters(includePrivateParameters: false); - if (key is RsaSecurityKey rsaSecurityKey) - { - // Note: if the RSA parameters are not attached to the signing key, - // extract them by calling ExportParameters on the RSA instance. - var parameters = rsaSecurityKey.Parameters; - if (parameters.Modulus is null) - { - parameters = rsaSecurityKey.Rsa.ExportParameters(includePrivateParameters: false); - - Debug.Assert(parameters.Modulus is not null, SR.GetResourceString(SR.ID4003)); - } - - // Only use the 40 first chars of the base64url-encoded modulus. - var identifier = Base64UrlEncoder.Encode(parameters.Modulus); - return identifier.Substring(0, Math.Min(identifier.Length, 40)).ToUpperInvariant(); + Debug.Assert(parameters.Modulus is not null, SR.GetResourceString(SR.ID4003)); } + // Only use the 40 first chars of the base64url-encoded modulus. + var identifier = Base64UrlEncoder.Encode(parameters.Modulus); + return identifier.Substring(0, Math.Min(identifier.Length, 40)).ToUpperInvariant(); + } + #if SUPPORTS_ECDSA - if (key is ECDsaSecurityKey ecsdaSecurityKey) - { - // Extract the ECDSA parameters from the signing credentials. - var parameters = ecsdaSecurityKey.ECDsa.ExportParameters(includePrivateParameters: false); + if (key is ECDsaSecurityKey ecsdaSecurityKey) + { + // Extract the ECDSA parameters from the signing credentials. + var parameters = ecsdaSecurityKey.ECDsa.ExportParameters(includePrivateParameters: false); - Debug.Assert(parameters.Q.X is not null, SR.GetResourceString(SR.ID4004)); + Debug.Assert(parameters.Q.X is not null, SR.GetResourceString(SR.ID4004)); - // Only use the 40 first chars of the base64url-encoded X coordinate. - var identifier = Base64UrlEncoder.Encode(parameters.Q.X); - return identifier.Substring(0, Math.Min(identifier.Length, 40)).ToUpperInvariant(); - } + // Only use the 40 first chars of the base64url-encoded X coordinate. + var identifier = Base64UrlEncoder.Encode(parameters.Q.X); + return identifier.Substring(0, Math.Min(identifier.Length, 40)).ToUpperInvariant(); + } #endif - return null; - } + return null; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerDispatcher.cs b/src/OpenIddict.Server/OpenIddictServerDispatcher.cs index 00cc0b4f..ca420ebf 100644 --- a/src/OpenIddict.Server/OpenIddictServerDispatcher.cs +++ b/src/OpenIddict.Server/OpenIddictServerDispatcher.cs @@ -12,127 +12,126 @@ using Microsoft.Extensions.Options; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public class OpenIddictServerDispatcher : IOpenIddictServerDispatcher { - public class OpenIddictServerDispatcher : IOpenIddictServerDispatcher + private readonly ILogger _logger; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + /// + /// Creates a new instance of the class. + /// + public OpenIddictServerDispatcher( + ILogger logger, + IOptionsMonitor options, + IServiceProvider provider) + { + _logger = logger; + _options = options; + _provider = provider; + } + + public async ValueTask DispatchAsync(TContext context) where TContext : BaseContext { - private readonly ILogger _logger; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - /// - /// Creates a new instance of the class. - /// - public OpenIddictServerDispatcher( - ILogger logger, - IOptionsMonitor options, - IServiceProvider provider) + if (context is null) { - _logger = logger; - _options = options; - _provider = provider; + throw new ArgumentNullException(nameof(context)); } - public async ValueTask DispatchAsync(TContext context) where TContext : BaseContext + await foreach (var handler in GetHandlersAsync()) { - if (context is null) + try { - throw new ArgumentNullException(nameof(context)); + await handler.HandleAsync(context); } - await foreach (var handler in GetHandlersAsync()) + catch (Exception exception) when (_logger.IsEnabled(LogLevel.Debug)) { - try - { - await handler.HandleAsync(context); - } + _logger.LogDebug(exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); - catch (Exception exception) when (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); - - throw; - } - - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName); - } - - switch (context) - { - case BaseRequestContext { IsRequestHandled: true }: - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName); - } - return; - - case BaseRequestContext { IsRequestSkipped: true }: - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName); - } - return; - - case BaseValidatingContext { IsRejected: true }: - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName); - } - return; - - default: continue; - } + throw; } - async IAsyncEnumerable> GetHandlersAsync() + if (_logger.IsEnabled(LogLevel.Debug)) { - // Note: the descriptors collection is sorted during options initialization for performance reasons. - var descriptors = _options.CurrentValue.Handlers; - if (descriptors.Count == 0) - { - yield break; - } + _logger.LogDebug(SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName); + } - for (var index = 0; index < descriptors.Count; index++) - { - var descriptor = descriptors[index]; - if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor)) + switch (context) + { + case BaseRequestContext { IsRequestHandled: true }: + if (_logger.IsEnabled(LogLevel.Debug)) { - continue; + _logger.LogDebug(SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName); } + return; - var handler = descriptor.ServiceDescriptor.ImplementationInstance is not null ? - descriptor.ServiceDescriptor.ImplementationInstance as IOpenIddictServerHandler : - _provider.GetService(descriptor.ServiceDescriptor.ServiceType) as IOpenIddictServerHandler; + case BaseRequestContext { IsRequestSkipped: true }: + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug(SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName); + } + return; - if (handler is null) + case BaseValidatingContext { IsRejected: true }: + if (_logger.IsEnabled(LogLevel.Debug)) { - throw new InvalidOperationException(SR.FormatID0098(descriptor.ServiceDescriptor.ServiceType)); + _logger.LogDebug(SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName); } + return; - yield return handler; - } + default: continue; + } + } + + async IAsyncEnumerable> GetHandlersAsync() + { + // Note: the descriptors collection is sorted during options initialization for performance reasons. + var descriptors = _options.CurrentValue.Handlers; + if (descriptors.Count == 0) + { + yield break; } - async ValueTask IsActiveAsync(OpenIddictServerHandlerDescriptor descriptor) + for (var index = 0; index < descriptors.Count; index++) { - for (var index = 0; index < descriptor.FilterTypes.Length; index++) + var descriptor = descriptors[index]; + if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor)) { - if (!(_provider.GetService(descriptor.FilterTypes[index]) is IOpenIddictServerHandlerFilter filter)) - { - throw new InvalidOperationException(SR.FormatID0099(descriptor.FilterTypes[index])); - } + continue; + } - if (!await filter.IsActiveAsync(context)) - { - return false; - } + var handler = descriptor.ServiceDescriptor.ImplementationInstance is not null ? + descriptor.ServiceDescriptor.ImplementationInstance as IOpenIddictServerHandler : + _provider.GetService(descriptor.ServiceDescriptor.ServiceType) as IOpenIddictServerHandler; + + if (handler is null) + { + throw new InvalidOperationException(SR.FormatID0098(descriptor.ServiceDescriptor.ServiceType)); } - return true; + yield return handler; } } + + async ValueTask IsActiveAsync(OpenIddictServerHandlerDescriptor descriptor) + { + for (var index = 0; index < descriptor.FilterTypes.Length; index++) + { + if (!(_provider.GetService(descriptor.FilterTypes[index]) is IOpenIddictServerHandlerFilter filter)) + { + throw new InvalidOperationException(SR.FormatID0099(descriptor.FilterTypes[index])); + } + + if (!await filter.IsActiveAsync(context)) + { + return false; + } + } + + return true; + } } } diff --git a/src/OpenIddict.Server/OpenIddictServerEndpointType.cs b/src/OpenIddict.Server/OpenIddictServerEndpointType.cs index 70094f34..b140dae3 100644 --- a/src/OpenIddict.Server/OpenIddictServerEndpointType.cs +++ b/src/OpenIddict.Server/OpenIddictServerEndpointType.cs @@ -4,66 +4,65 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Server +namespace OpenIddict.Server; + +/// +/// Represents the type of an OpenIddict server endpoint. +/// +public enum OpenIddictServerEndpointType { /// - /// Represents the type of an OpenIddict server endpoint. + /// Unknown endpoint. /// - public enum OpenIddictServerEndpointType - { - /// - /// Unknown endpoint. - /// - Unknown = 0, + Unknown = 0, - /// - /// Authorization endpoint. - /// - Authorization = 1, + /// + /// Authorization endpoint. + /// + Authorization = 1, - /// - /// Token endpoint. - /// - Token = 2, + /// + /// Token endpoint. + /// + Token = 2, - /// - /// Logout endpoint. - /// - Logout = 3, + /// + /// Logout endpoint. + /// + Logout = 3, - /// - /// Configuration endpoint. - /// - Configuration = 4, + /// + /// Configuration endpoint. + /// + Configuration = 4, - /// - /// Cryptography endpoint. - /// - Cryptography = 5, + /// + /// Cryptography endpoint. + /// + Cryptography = 5, - /// - /// Userinfo endpoint. - /// - Userinfo = 6, + /// + /// Userinfo endpoint. + /// + Userinfo = 6, - /// - /// Introspection endpoint. - /// - Introspection = 7, + /// + /// Introspection endpoint. + /// + Introspection = 7, - /// - /// Revocation endpoint. - /// - Revocation = 8, + /// + /// Revocation endpoint. + /// + Revocation = 8, - /// - /// Device endpoint. - /// - Device = 9, + /// + /// Device endpoint. + /// + Device = 9, - /// - /// Verification endpoint. - /// - Verification = 10 - } + /// + /// Verification endpoint. + /// + Verification = 10 } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Authentication.cs index 7dd1ab7d..fa7104d7 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Authentication.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Authentication.cs @@ -10,204 +10,203 @@ using System.Security.Claims; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an event called for each request to the authorization endpoint to give the user code + /// a chance to manually extract the authorization request from the ambient HTTP context. + /// + public class ExtractAuthorizationRequestContext : BaseValidatingContext { /// - /// Represents an event called for each request to the authorization endpoint to give the user code - /// a chance to manually extract the authorization request from the ambient HTTP context. + /// Creates a new instance of the class. /// - public class ExtractAuthorizationRequestContext : BaseValidatingContext + public ExtractAuthorizationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ExtractAuthorizationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request or null if it was extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } } /// - /// Represents an event called for each request to the authorization endpoint - /// to determine if the request is valid and should continue to be processed. + /// Gets or sets the request or null if it was extracted yet. /// - public class ValidateAuthorizationRequestContext : BaseValidatingContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ValidateAuthorizationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - // Infer the redirect_uri from the value specified by the client application. - => RedirectUri = Request?.RedirectUri; - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + get => Transaction.Request; + set => Transaction.Request = value; + } + } - /// - /// Gets the client_id specified by the client application. - /// - public string? ClientId => Request?.ClientId; - - /// - /// Gets the redirect_uri specified by the client application. - /// If it's not provided by the client, it must be set by - /// the user code by calling . - /// - public string? RedirectUri { get; private set; } - - /// - /// Populates the property with the specified redirect_uri. - /// - /// The redirect_uri to use when redirecting the user agent. - public void SetRedirectUri(string address) - { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0100), nameof(address)); - } - - // Don't allow validation to alter the redirect_uri parameter extracted - // from the request if the address was explicitly provided by the client. - if (!string.IsNullOrEmpty(Request?.RedirectUri) && - !string.Equals(Request.RedirectUri, address, StringComparison.Ordinal)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0101)); - } - - RedirectUri = address; - } + /// + /// Represents an event called for each request to the authorization endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateAuthorizationRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ValidateAuthorizationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + // Infer the redirect_uri from the value specified by the client application. + => RedirectUri = Request?.RedirectUri; + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; } /// - /// Represents an event called for each validated authorization request - /// to allow the user code to decide how the request should be handled. + /// Gets the client_id specified by the client application. + /// + public string? ClientId => Request?.ClientId; + + /// + /// Gets the redirect_uri specified by the client application. + /// If it's not provided by the client, it must be set by + /// the user code by calling . + /// + public string? RedirectUri { get; private set; } + + /// + /// Populates the property with the specified redirect_uri. /// - public class HandleAuthorizationRequestContext : BaseValidatingTicketContext + /// The redirect_uri to use when redirecting the user agent. + public void SetRedirectUri(string address) { - /// - /// Creates a new instance of the class. - /// - public HandleAuthorizationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) + if (string.IsNullOrEmpty(address)) { + throw new ArgumentException(SR.GetResourceString(SR.ID0100), nameof(address)); } - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request + // Don't allow validation to alter the redirect_uri parameter extracted + // from the request if the address was explicitly provided by the client. + if (!string.IsNullOrEmpty(Request?.RedirectUri) && + !string.Equals(Request.RedirectUri, address, StringComparison.Ordinal)) { - get => Transaction.Request!; - set => Transaction.Request = value; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0101)); } - /// - /// Gets the additional parameters returned to the client application. - /// - public Dictionary Parameters { get; private set; } - = new(StringComparer.Ordinal); - - /// - /// Allows OpenIddict to return a sign-in response using the specified principal. - /// - /// The claims principal. - public void SignIn(ClaimsPrincipal principal) => Principal = principal; - - /// - /// Allows OpenIddict to return a sign-in response using the specified principal. - /// - /// The claims principal. - /// The additional parameters returned to the client application. - public void SignIn(ClaimsPrincipal principal, IDictionary parameters) - { - Principal = principal; - Parameters = new(parameters, StringComparer.Ordinal); - } + RedirectUri = address; } + } + /// + /// Represents an event called for each validated authorization request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleAuthorizationRequestContext : BaseValidatingTicketContext + { /// - /// Represents an event called before the authorization response is returned to the caller. + /// Creates a new instance of the class. /// - public class ApplyAuthorizationResponseContext : BaseRequestContext + public HandleAuthorizationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ApplyAuthorizationResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + } - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } + /// + /// Gets the additional parameters returned to the client application. + /// + public Dictionary Parameters { get; private set; } + = new(StringComparer.Ordinal); + + /// + /// Allows OpenIddict to return a sign-in response using the specified principal. + /// + /// The claims principal. + public void SignIn(ClaimsPrincipal principal) => Principal = principal; + + /// + /// Allows OpenIddict to return a sign-in response using the specified principal. + /// + /// The claims principal. + /// The additional parameters returned to the client application. + public void SignIn(ClaimsPrincipal principal, IDictionary parameters) + { + Principal = principal; + Parameters = new(parameters, StringComparer.Ordinal); + } + } + + /// + /// Represents an event called before the authorization response is returned to the caller. + /// + public class ApplyAuthorizationResponseContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyAuthorizationResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request, or null if it couldn't be extracted. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } - /// - /// Gets the access code expected to - /// be returned to the client application. - /// Depending on the flow, it may be null. - /// - public string? AccessToken => Response?.AccessToken; - - /// - /// Gets the authorization code expected to - /// be returned to the client application. - /// Depending on the flow, it may be null. - /// - public string? AuthorizationCode => Response?.Code; - - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response?.Error; - - /// - /// Gets or sets the callback URL the user agent will be redirected to, if applicable. - /// Note: manually changing the value of this property is generally not recommended - /// and extreme caution must be taken to ensure the user agent is not redirected to - /// an untrusted address, which would result in an "open redirection" vulnerability. - /// - public string? RedirectUri { get; set; } - - /// - /// Gets or sets the response mode used to redirect the user agent, if applicable. - /// Note: manually changing the value of this property is generally not recommended. - /// - public string? ResponseMode { get; set; } + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; } + + /// + /// Gets the access code expected to + /// be returned to the client application. + /// Depending on the flow, it may be null. + /// + public string? AccessToken => Response?.AccessToken; + + /// + /// Gets the authorization code expected to + /// be returned to the client application. + /// Depending on the flow, it may be null. + /// + public string? AuthorizationCode => Response?.Code; + + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response?.Error; + + /// + /// Gets or sets the callback URL the user agent will be redirected to, if applicable. + /// Note: manually changing the value of this property is generally not recommended + /// and extreme caution must be taken to ensure the user agent is not redirected to + /// an untrusted address, which would result in an "open redirection" vulnerability. + /// + public string? RedirectUri { get; set; } + + /// + /// Gets or sets the response mode used to redirect the user agent, if applicable. + /// Note: manually changing the value of this property is generally not recommended. + /// + public string? ResponseMode { get; set; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Device.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Device.cs index e85ca151..8e6c557c 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Device.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Device.cs @@ -9,286 +9,285 @@ using System.Collections.Generic; using System.Security.Claims; using OpenIddict.Abstractions; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an event called for each request to the device endpoint to give the user code + /// a chance to manually extract the device request from the ambient HTTP context. + /// + public class ExtractDeviceRequestContext : BaseValidatingContext { /// - /// Represents an event called for each request to the device endpoint to give the user code - /// a chance to manually extract the device request from the ambient HTTP context. + /// Creates a new instance of the class. /// - public class ExtractDeviceRequestContext : BaseValidatingContext + public ExtractDeviceRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ExtractDeviceRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + } - /// - /// Gets or sets the request, or null if it wasn't extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } + /// + /// Gets or sets the request, or null if it wasn't extracted yet. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; } + } + /// + /// Represents an event called for each request to the device endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateDeviceRequestContext : BaseValidatingClientContext + { /// - /// Represents an event called for each request to the device endpoint - /// to determine if the request is valid and should continue to be processed. + /// Creates a new instance of the class. /// - public class ValidateDeviceRequestContext : BaseValidatingClientContext + public ValidateDeviceRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ValidateDeviceRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + } - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; } + } + /// + /// Represents an event called for each validated device request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleDeviceRequestContext : BaseValidatingTicketContext + { /// - /// Represents an event called for each validated device request - /// to allow the user code to decide how the request should be handled. + /// Creates a new instance of the class. /// - public class HandleDeviceRequestContext : BaseValidatingTicketContext + public HandleDeviceRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public HandleDeviceRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + } - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } - /// - /// Gets the additional parameters returned to the client application. - /// - public Dictionary Parameters { get; private set; } - = new(StringComparer.Ordinal); + /// + /// Gets the additional parameters returned to the client application. + /// + public Dictionary Parameters { get; private set; } + = new(StringComparer.Ordinal); - /// - /// Allows OpenIddict to return a sign-in response using the specified principal. - /// - /// The claims principal. - public void SignIn(ClaimsPrincipal principal) => Principal = principal; + /// + /// Allows OpenIddict to return a sign-in response using the specified principal. + /// + /// The claims principal. + public void SignIn(ClaimsPrincipal principal) => Principal = principal; + + /// + /// Allows OpenIddict to return a sign-in response using the specified principal. + /// + /// The claims principal. + /// The additional parameters returned to the client application. + public void SignIn(ClaimsPrincipal principal, IDictionary parameters) + { + Principal = principal; + Parameters = new(parameters, StringComparer.Ordinal); + } + } - /// - /// Allows OpenIddict to return a sign-in response using the specified principal. - /// - /// The claims principal. - /// The additional parameters returned to the client application. - public void SignIn(ClaimsPrincipal principal, IDictionary parameters) - { - Principal = principal; - Parameters = new(parameters, StringComparer.Ordinal); - } + /// + /// Represents an event called before the device response is returned to the caller. + /// + public class ApplyDeviceResponseContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyDeviceResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) + { } /// - /// Represents an event called before the device response is returned to the caller. + /// Gets or sets the request, or null if it couldn't be extracted. /// - public class ApplyDeviceResponseContext : BaseRequestContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ApplyDeviceResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + get => Transaction.Request; + set => Transaction.Request = value; + } - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response.Error; + } - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response.Error; + /// + /// Represents an event called for each request to the verification endpoint to give the user code + /// a chance to manually extract the verification request from the ambient HTTP context. + /// + public class ExtractVerificationRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ExtractVerificationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { } /// - /// Represents an event called for each request to the verification endpoint to give the user code - /// a chance to manually extract the verification request from the ambient HTTP context. + /// Gets or sets the request, or null if it wasn't extracted yet. /// - public class ExtractVerificationRequestContext : BaseValidatingContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ExtractVerificationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + get => Transaction.Request; + set => Transaction.Request = value; + } + } - /// - /// Gets or sets the request, or null if it wasn't extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } + /// + /// Represents an event called for each request to the verification endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateVerificationRequestContext : BaseValidatingClientContext + { + /// + /// Creates a new instance of the class. + /// + public ValidateVerificationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { } /// - /// Represents an event called for each request to the verification endpoint - /// to determine if the request is valid and should continue to be processed. + /// Gets or sets the request. /// - public class ValidateVerificationRequestContext : BaseValidatingClientContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public ValidateVerificationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + get => Transaction.Request!; + set => Transaction.Request = value; + } - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + /// + /// Gets or sets the security principal extracted from the user code. + /// + public ClaimsPrincipal? Principal { get; set; } + } - /// - /// Gets or sets the security principal extracted from the user code. - /// - public ClaimsPrincipal? Principal { get; set; } + /// + /// Represents an event called for each validated verification request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleVerificationRequestContext : BaseValidatingTicketContext + { + /// + /// Creates a new instance of the class. + /// + public HandleVerificationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { } /// - /// Represents an event called for each validated verification request - /// to allow the user code to decide how the request should be handled. + /// Gets or sets the request. /// - public class HandleVerificationRequestContext : BaseValidatingTicketContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public HandleVerificationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + get => Transaction.Request!; + set => Transaction.Request = value; + } - /// - /// Gets the additional parameters returned to the caller. - /// - /// - /// Note: by default, this property is not used as empty responses are typically - /// returned for user verification requests. To return a different response, a - /// custom event handler must be registered to handle user verification responses. - /// - public Dictionary Parameters { get; private set; } - = new(StringComparer.Ordinal); + /// + /// Gets the additional parameters returned to the caller. + /// + /// + /// Note: by default, this property is not used as empty responses are typically + /// returned for user verification requests. To return a different response, a + /// custom event handler must be registered to handle user verification responses. + /// + public Dictionary Parameters { get; private set; } + = new(StringComparer.Ordinal); - /// - /// Allows OpenIddict to return a sign-in response using the specified principal. - /// - /// The claims principal. - public void SignIn(ClaimsPrincipal principal) => Principal = principal; + /// + /// Allows OpenIddict to return a sign-in response using the specified principal. + /// + /// The claims principal. + public void SignIn(ClaimsPrincipal principal) => Principal = principal; - /// - /// Allows OpenIddict to return a sign-in response using the specified principal. - /// - /// The claims principal. - /// The additional parameters returned to the client application. - public void SignIn(ClaimsPrincipal principal, IDictionary parameters) - { - Principal = principal; - Parameters = new(parameters, StringComparer.Ordinal); - } + /// + /// Allows OpenIddict to return a sign-in response using the specified principal. + /// + /// The claims principal. + /// The additional parameters returned to the client application. + public void SignIn(ClaimsPrincipal principal, IDictionary parameters) + { + Principal = principal; + Parameters = new(parameters, StringComparer.Ordinal); } + } + /// + /// Represents an event called before the verification response is returned to the caller. + /// + public class ApplyVerificationResponseContext : BaseRequestContext + { /// - /// Represents an event called before the verification response is returned to the caller. + /// Creates a new instance of the class. /// - public class ApplyVerificationResponseContext : BaseRequestContext + public ApplyVerificationResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ApplyVerificationResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } + } - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } + /// + /// Gets or sets the request, or null if it couldn't be extracted. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response.Error; + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; } + + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response.Error; } } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Discovery.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Discovery.cs index 00484ac6..d51cb2b8 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Discovery.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Discovery.cs @@ -9,354 +9,353 @@ using System.Collections.Generic; using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an event called for each request to the configuration endpoint to give the user code + /// a chance to manually extract the configuration request from the ambient HTTP context. + /// + public class ExtractConfigurationRequestContext : BaseValidatingContext { /// - /// Represents an event called for each request to the configuration endpoint to give the user code - /// a chance to manually extract the configuration request from the ambient HTTP context. + /// Creates a new instance of the class. /// - public class ExtractConfigurationRequestContext : BaseValidatingContext + public ExtractConfigurationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ExtractConfigurationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it wasn't extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } } /// - /// Represents an event called for each request to the configuration endpoint - /// to determine if the request is valid and should continue to be processed. + /// Gets or sets the request, or null if it wasn't extracted yet. /// - public class ValidateConfigurationRequestContext : BaseValidatingContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ValidateConfigurationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + get => Transaction.Request; + set => Transaction.Request = value; } + } + /// + /// Represents an event called for each request to the configuration endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateConfigurationRequestContext : BaseValidatingContext + { /// - /// Represents an event called for each validated configuration request - /// to allow the user code to decide how the request should be handled. + /// Creates a new instance of the class. /// - public class HandleConfigurationRequestContext : BaseValidatingContext + public ValidateConfigurationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public HandleConfigurationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets the additional parameters returned to the client application. - /// - public Dictionary Metadata { get; } = new(StringComparer.Ordinal); - - /// - /// Gets or sets the authorization endpoint address. - /// - public Uri? AuthorizationEndpoint { get; set; } - - /// - /// Gets or sets the JWKS endpoint address. - /// - public Uri? CryptographyEndpoint { get; set; } - - /// - /// Gets or sets the device endpoint address. - /// - public Uri? DeviceEndpoint { get; set; } - - /// - /// Gets or sets the introspection endpoint address. - /// - public Uri? IntrospectionEndpoint { get; set; } - - /// - /// Gets or sets the logout endpoint address. - /// - public Uri? LogoutEndpoint { get; set; } - - /// - /// Gets or sets the revocation endpoint address. - /// - public Uri? RevocationEndpoint { get; set; } - - /// - /// Gets or sets the token endpoint address. - /// - public Uri? TokenEndpoint { get; set; } - - /// - /// Gets or sets the userinfo endpoint address. - /// - public Uri? UserinfoEndpoint { get; set; } - - /// - /// Gets the list of claims supported by the authorization server. - /// - public HashSet Claims { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets a list of the code challenge methods - /// supported by the authorization server. - /// - public HashSet CodeChallengeMethods { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets the list of grant types - /// supported by the authorization server. - /// - public HashSet GrantTypes { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets a list of signing algorithms supported by the - /// authorization server for signing the identity tokens. - /// - public HashSet IdTokenSigningAlgorithms { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets a list of client authentication methods supported by - /// the introspection endpoint provided by the authorization server. - /// - public HashSet IntrospectionEndpointAuthenticationMethods { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets the list of response modes - /// supported by the authorization server. - /// - public HashSet ResponseModes { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets the list of response types - /// supported by the authorization server. - /// - public HashSet ResponseTypes { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets a list of client authentication methods supported by - /// the revocation endpoint provided by the authorization server. - /// - public HashSet RevocationEndpointAuthenticationMethods { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets the list of scope values - /// supported by the authorization server. - /// - public HashSet Scopes { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets the list of subject types - /// supported by the authorization server. - /// - public HashSet SubjectTypes { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets a list of client authentication methods supported by - /// the token endpoint provided by the authorization server. - /// - public HashSet TokenEndpointAuthenticationMethods { get; } = new HashSet(StringComparer.Ordinal); } /// - /// Represents an event called before the configuration response is returned to the caller. + /// Gets or sets the request. /// - public class ApplyConfigurationResponseContext : BaseRequestContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public ApplyConfigurationResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response.Error; + get => Transaction.Request!; + set => Transaction.Request = value; } + } + /// + /// Represents an event called for each validated configuration request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleConfigurationRequestContext : BaseValidatingContext + { /// - /// Represents an event called for each request to the cryptography endpoint to give the user code - /// a chance to manually extract the cryptography request from the ambient HTTP context. + /// Creates a new instance of the class. /// - public class ExtractCryptographyRequestContext : BaseValidatingContext + public HandleConfigurationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ExtractCryptographyRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it wasn't extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } } /// - /// Represents an event called for each request to the cryptography endpoint - /// to determine if the request is valid and should continue to be processed. + /// Gets or sets the request. /// - public class ValidateCryptographyRequestContext : BaseValidatingContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public ValidateCryptographyRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + get => Transaction.Request!; + set => Transaction.Request = value; } /// - /// Represents an event called for each validated cryptography request - /// to allow the user code to decide how the request should be handled. + /// Gets the additional parameters returned to the client application. + /// + public Dictionary Metadata { get; } = new(StringComparer.Ordinal); + + /// + /// Gets or sets the authorization endpoint address. + /// + public Uri? AuthorizationEndpoint { get; set; } + + /// + /// Gets or sets the JWKS endpoint address. + /// + public Uri? CryptographyEndpoint { get; set; } + + /// + /// Gets or sets the device endpoint address. + /// + public Uri? DeviceEndpoint { get; set; } + + /// + /// Gets or sets the introspection endpoint address. + /// + public Uri? IntrospectionEndpoint { get; set; } + + /// + /// Gets or sets the logout endpoint address. + /// + public Uri? LogoutEndpoint { get; set; } + + /// + /// Gets or sets the revocation endpoint address. + /// + public Uri? RevocationEndpoint { get; set; } + + /// + /// Gets or sets the token endpoint address. + /// + public Uri? TokenEndpoint { get; set; } + + /// + /// Gets or sets the userinfo endpoint address. + /// + public Uri? UserinfoEndpoint { get; set; } + + /// + /// Gets the list of claims supported by the authorization server. + /// + public HashSet Claims { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets a list of the code challenge methods + /// supported by the authorization server. /// - public class HandleCryptographyRequestContext : BaseValidatingContext + public HashSet CodeChallengeMethods { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets the list of grant types + /// supported by the authorization server. + /// + public HashSet GrantTypes { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets a list of signing algorithms supported by the + /// authorization server for signing the identity tokens. + /// + public HashSet IdTokenSigningAlgorithms { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets a list of client authentication methods supported by + /// the introspection endpoint provided by the authorization server. + /// + public HashSet IntrospectionEndpointAuthenticationMethods { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets the list of response modes + /// supported by the authorization server. + /// + public HashSet ResponseModes { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets the list of response types + /// supported by the authorization server. + /// + public HashSet ResponseTypes { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets a list of client authentication methods supported by + /// the revocation endpoint provided by the authorization server. + /// + public HashSet RevocationEndpointAuthenticationMethods { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets the list of scope values + /// supported by the authorization server. + /// + public HashSet Scopes { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets the list of subject types + /// supported by the authorization server. + /// + public HashSet SubjectTypes { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets a list of client authentication methods supported by + /// the token endpoint provided by the authorization server. + /// + public HashSet TokenEndpointAuthenticationMethods { get; } = new HashSet(StringComparer.Ordinal); + } + + /// + /// Represents an event called before the configuration response is returned to the caller. + /// + public class ApplyConfigurationResponseContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyConfigurationResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public HandleCryptographyRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets the list of JSON Web Keys exposed by the JWKS endpoint. - /// - public List Keys { get; } = new List(); } /// - /// Represents an event called before the cryptography response is returned to the caller. + /// Gets or sets the request, or null if it couldn't be extracted. /// - public class ApplyCryptographyResponseContext : BaseRequestContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ApplyCryptographyResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response.Error; + get => Transaction.Request; + set => Transaction.Request = value; } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response.Error; + } + + /// + /// Represents an event called for each request to the cryptography endpoint to give the user code + /// a chance to manually extract the cryptography request from the ambient HTTP context. + /// + public class ExtractCryptographyRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ExtractCryptographyRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request, or null if it wasn't extracted yet. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + } + + /// + /// Represents an event called for each request to the cryptography endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateCryptographyRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ValidateCryptographyRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + } + + /// + /// Represents an event called for each validated cryptography request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleCryptographyRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public HandleCryptographyRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets the list of JSON Web Keys exposed by the JWKS endpoint. + /// + public List Keys { get; } = new List(); + } + + /// + /// Represents an event called before the cryptography response is returned to the caller. + /// + public class ApplyCryptographyResponseContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyCryptographyResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request, or null if it couldn't be extracted. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response.Error; } } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Exchange.cs index 64c7cc8c..7b02a2b0 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Exchange.cs @@ -9,148 +9,147 @@ using System.Collections.Generic; using System.Security.Claims; using OpenIddict.Abstractions; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an event called for each request to the token endpoint to give the user code + /// a chance to manually extract the token request from the ambient HTTP context. + /// + public class ExtractTokenRequestContext : BaseValidatingContext { /// - /// Represents an event called for each request to the token endpoint to give the user code - /// a chance to manually extract the token request from the ambient HTTP context. + /// Creates a new instance of the class. /// - public class ExtractTokenRequestContext : BaseValidatingContext + public ExtractTokenRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ExtractTokenRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it wasn't extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } } /// - /// Represents an event called for each request to the token endpoint - /// to determine if the request is valid and should continue to be processed. + /// Gets or sets the request, or null if it wasn't extracted yet. /// - public class ValidateTokenRequestContext : BaseValidatingClientContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ValidateTokenRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the security principal extracted from the authorization - /// code or the refresh token, if applicable to the current token request. - /// - public ClaimsPrincipal? Principal { get; set; } + get => Transaction.Request; + set => Transaction.Request = value; } + } + /// + /// Represents an event called for each request to the token endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateTokenRequestContext : BaseValidatingClientContext + { /// - /// Represents an event called for each validated token request - /// to allow the user code to decide how the request should be handled. + /// Creates a new instance of the class. /// - public class HandleTokenRequestContext : BaseValidatingTicketContext + public ValidateTokenRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public HandleTokenRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets the additional parameters returned to the client application. - /// - public Dictionary Parameters { get; private set; } - = new(StringComparer.Ordinal); - - /// - /// Allows OpenIddict to return a sign-in response using the specified principal. - /// - /// The claims principal. - public void SignIn(ClaimsPrincipal principal) => Principal = principal; - - /// - /// Allows OpenIddict to return a sign-in response using the specified principal. - /// - /// The claims principal. - /// The additional parameters returned to the client application. - public void SignIn(ClaimsPrincipal principal, IDictionary parameters) - { - Principal = principal; - Parameters = new(parameters, StringComparer.Ordinal); - } } /// - /// Represents an event called before the token response is returned to the caller. + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the security principal extracted from the authorization + /// code or the refresh token, if applicable to the current token request. + /// + public ClaimsPrincipal? Principal { get; set; } + } + + /// + /// Represents an event called for each validated token request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleTokenRequestContext : BaseValidatingTicketContext + { + /// + /// Creates a new instance of the class. /// - public class ApplyTokenResponseContext : BaseRequestContext + public HandleTokenRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ApplyTokenResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response.Error; } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets the additional parameters returned to the client application. + /// + public Dictionary Parameters { get; private set; } + = new(StringComparer.Ordinal); + + /// + /// Allows OpenIddict to return a sign-in response using the specified principal. + /// + /// The claims principal. + public void SignIn(ClaimsPrincipal principal) => Principal = principal; + + /// + /// Allows OpenIddict to return a sign-in response using the specified principal. + /// + /// The claims principal. + /// The additional parameters returned to the client application. + public void SignIn(ClaimsPrincipal principal, IDictionary parameters) + { + Principal = principal; + Parameters = new(parameters, StringComparer.Ordinal); + } + } + + /// + /// Represents an event called before the token response is returned to the caller. + /// + public class ApplyTokenResponseContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyTokenResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request, or null if it couldn't be extracted. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response.Error; } } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Introspection.cs index 5101bdb1..ce8cada3 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Introspection.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Introspection.cs @@ -9,205 +9,204 @@ using System.Collections.Generic; using System.Security.Claims; using OpenIddict.Abstractions; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an event called for each request to the introspection endpoint to give the user code + /// a chance to manually extract the introspection request from the ambient HTTP context. + /// + public class ExtractIntrospectionRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ExtractIntrospectionRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request, or null if it wasn't extracted yet. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } + } + + /// + /// Represents an event called for each request to the introspection endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateIntrospectionRequestContext : BaseValidatingClientContext + { + /// + /// Creates a new instance of the class. + /// + public ValidateIntrospectionRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets the optional token_type_hint parameter extracted from the + /// introspection request, or null if it cannot be found. + /// + public string? TokenTypeHint => Request?.TokenTypeHint; + + /// + /// Gets or sets the security principal extracted from the introspected token, if available. + /// + public ClaimsPrincipal? Principal { get; set; } + } + + /// + /// Represents an event called for each validated introspection request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleIntrospectionRequestContext : BaseValidatingContext { /// - /// Represents an event called for each request to the introspection endpoint to give the user code - /// a chance to manually extract the introspection request from the ambient HTTP context. + /// Creates a new instance of the class. + /// + public HandleIntrospectionRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. /// - public class ExtractIntrospectionRequestContext : BaseValidatingContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public ExtractIntrospectionRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it wasn't extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } + get => Transaction.Request!; + set => Transaction.Request = value; } /// - /// Represents an event called for each request to the introspection endpoint - /// to determine if the request is valid and should continue to be processed. + /// Gets or sets the security principal extracted from the introspected token. + /// + public ClaimsPrincipal Principal { get; set; } = default!; + + /// + /// Gets the additional claims returned to the client application. + /// + public Dictionary Claims { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the list of audiences returned to the caller + /// as part of the "aud" claim, if applicable. + /// + public HashSet Audiences { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets or sets the "client_id" claim returned to the caller, if applicable. + /// + public string? ClientId { get; set; } + + /// + /// Gets or sets the "exp" claim + /// returned to the caller, if applicable. + /// + public DateTimeOffset? ExpiresAt { get; set; } + + /// + /// Gets or sets the "iat" claim + /// returned to the caller, if applicable. + /// + public DateTimeOffset? IssuedAt { get; set; } + + /// + /// Gets or sets the "nbf" claim + /// returned to the caller, if applicable. + /// + public DateTimeOffset? NotBefore { get; set; } + + /// + /// Gets the list of scopes returned to the caller + /// as part of the "scope" claim, if applicable. + /// + public HashSet Scopes { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets or sets the "sub" claim + /// returned to the caller, if applicable. /// - public class ValidateIntrospectionRequestContext : BaseValidatingClientContext + public string? Subject { get; set; } + + /// + /// Gets or sets the "jti" claim + /// returned to the caller, if applicable. + /// + public string? TokenId { get; set; } + + /// + /// Gets or sets the "token_type" claim + /// returned to the caller, if applicable. + /// + public string? TokenType { get; set; } + + /// + /// Gets or sets the "token_usage" claim + /// returned to the caller, if applicable. + /// + public string? TokenUsage { get; set; } + + /// + /// Gets or sets the "username" claim + /// returned to the caller, if applicable. + /// + public string? Username { get; set; } + } + + /// + /// Represents an event called before the introspection response is returned to the caller. + /// + public class ApplyIntrospectionResponseContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyIntrospectionResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ValidateIntrospectionRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets the optional token_type_hint parameter extracted from the - /// introspection request, or null if it cannot be found. - /// - public string? TokenTypeHint => Request?.TokenTypeHint; - - /// - /// Gets or sets the security principal extracted from the introspected token, if available. - /// - public ClaimsPrincipal? Principal { get; set; } } /// - /// Represents an event called for each validated introspection request - /// to allow the user code to decide how the request should be handled. + /// Gets or sets the request, or null if it couldn't be extracted. /// - public class HandleIntrospectionRequestContext : BaseValidatingContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public HandleIntrospectionRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the security principal extracted from the introspected token. - /// - public ClaimsPrincipal Principal { get; set; } = default!; - - /// - /// Gets the additional claims returned to the client application. - /// - public Dictionary Claims { get; } = new(StringComparer.Ordinal); - - /// - /// Gets the list of audiences returned to the caller - /// as part of the "aud" claim, if applicable. - /// - public HashSet Audiences { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets or sets the "client_id" claim returned to the caller, if applicable. - /// - public string? ClientId { get; set; } - - /// - /// Gets or sets the "exp" claim - /// returned to the caller, if applicable. - /// - public DateTimeOffset? ExpiresAt { get; set; } - - /// - /// Gets or sets the "iat" claim - /// returned to the caller, if applicable. - /// - public DateTimeOffset? IssuedAt { get; set; } - - /// - /// Gets or sets the "nbf" claim - /// returned to the caller, if applicable. - /// - public DateTimeOffset? NotBefore { get; set; } - - /// - /// Gets the list of scopes returned to the caller - /// as part of the "scope" claim, if applicable. - /// - public HashSet Scopes { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets or sets the "sub" claim - /// returned to the caller, if applicable. - /// - public string? Subject { get; set; } - - /// - /// Gets or sets the "jti" claim - /// returned to the caller, if applicable. - /// - public string? TokenId { get; set; } - - /// - /// Gets or sets the "token_type" claim - /// returned to the caller, if applicable. - /// - public string? TokenType { get; set; } - - /// - /// Gets or sets the "token_usage" claim - /// returned to the caller, if applicable. - /// - public string? TokenUsage { get; set; } - - /// - /// Gets or sets the "username" claim - /// returned to the caller, if applicable. - /// - public string? Username { get; set; } + get => Transaction.Request; + set => Transaction.Request = value; } /// - /// Represents an event called before the introspection response is returned to the caller. + /// Gets or sets the response. /// - public class ApplyIntrospectionResponseContext : BaseRequestContext + public OpenIddictResponse Response { - /// - /// Creates a new instance of the class. - /// - public ApplyIntrospectionResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response.Error; + get => Transaction.Response!; + set => Transaction.Response = value; } + + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response.Error; } } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs index 4348dfcd..c2331757 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs @@ -11,142 +11,141 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an event called when generating a token. + /// + public class GenerateTokenContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public GenerateTokenContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request, or null if it is not available. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the client identifier of the application + /// the resulting token will be issued to, if applicable. + /// + public string? ClientId { get; set; } + + /// + /// Gets or sets a boolean indicating whether a token entry + /// should be created to persist token metadata in a database. + /// + public bool CreateTokenEntry { get; set; } + + /// + /// Gets or sets a boolean indicating whether the token payload + /// should be persisted alongside the token metadata in the database. + /// + public bool PersistTokenPayload { get; set; } + + /// + /// Gets or sets the security principal used to create the token. + /// + public ClaimsPrincipal Principal { get; set; } = default!; + + /// + /// Gets or sets the encryption credentials used to encrypt the token. + /// + public EncryptingCredentials? EncryptionCredentials { get; set; } + + /// + /// Gets or sets the signing credentials used to sign the token. + /// + public SigningCredentials? SigningCredentials { get; set; } + + /// + /// Gets or sets the security token handler used to serialize the security principal. + /// + public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!; + + /// + /// Gets or sets the token returned to the client application. + /// + public string? Token { get; set; } + + /// + /// Gets or sets the type of the token to create. + /// + public string TokenType { get; set; } = default!; + } + + /// + /// Represents an event called when validating a token. + /// + public class ValidateTokenContext : BaseValidatingContext { /// - /// Represents an event called when generating a token. + /// Creates a new instance of the class. /// - public class GenerateTokenContext : BaseValidatingContext + public ValidateTokenContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public GenerateTokenContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it is not available. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the client identifier of the application - /// the resulting token will be issued to, if applicable. - /// - public string? ClientId { get; set; } - - /// - /// Gets or sets a boolean indicating whether a token entry - /// should be created to persist token metadata in a database. - /// - public bool CreateTokenEntry { get; set; } - - /// - /// Gets or sets a boolean indicating whether the token payload - /// should be persisted alongside the token metadata in the database. - /// - public bool PersistTokenPayload { get; set; } - - /// - /// Gets or sets the security principal used to create the token. - /// - public ClaimsPrincipal Principal { get; set; } = default!; - - /// - /// Gets or sets the encryption credentials used to encrypt the token. - /// - public EncryptingCredentials? EncryptionCredentials { get; set; } - - /// - /// Gets or sets the signing credentials used to sign the token. - /// - public SigningCredentials? SigningCredentials { get; set; } - - /// - /// Gets or sets the security token handler used to serialize the security principal. - /// - public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!; - - /// - /// Gets or sets the token returned to the client application. - /// - public string? Token { get; set; } - - /// - /// Gets or sets the type of the token to create. - /// - public string TokenType { get; set; } = default!; } /// - /// Represents an event called when validating a token. + /// Gets or sets the request, or null if it is not available. /// - public class ValidateTokenContext : BaseValidatingContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ValidateTokenContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it is not available. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets a boolean indicating whether lifetime validation is disabled. - /// - public bool DisableLifetimeValidation { get; set; } - - /// - /// Gets or sets the security token handler used to validate the token. - /// - public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!; - - /// - /// Gets or sets the validation parameters used to verify the authenticity of tokens. - /// - public TokenValidationParameters TokenValidationParameters { get; set; } = default!; - - /// - /// Gets or sets the token to validate. - /// - public string Token { get; set; } = default!; - - /// - /// Gets or sets the token type hint specified by the client, if applicable. - /// - public string? TokenTypeHint { get; set; } = default!; - - /// - /// Gets or sets the token entry identifier associated with the token, if applicable. - /// - public string? TokenId { get; set; } - - /// - /// Gets or sets the security principal resolved from the token. - /// - public ClaimsPrincipal? Principal { get; set; } - - /// - /// Gets the token types that are considered valid. - /// - public HashSet ValidTokenTypes { get; } = new(StringComparer.OrdinalIgnoreCase); + get => Transaction.Request; + set => Transaction.Request = value; } + + /// + /// Gets or sets a boolean indicating whether lifetime validation is disabled. + /// + public bool DisableLifetimeValidation { get; set; } + + /// + /// Gets or sets the security token handler used to validate the token. + /// + public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!; + + /// + /// Gets or sets the validation parameters used to verify the authenticity of tokens. + /// + public TokenValidationParameters TokenValidationParameters { get; set; } = default!; + + /// + /// Gets or sets the token to validate. + /// + public string Token { get; set; } = default!; + + /// + /// Gets or sets the token type hint specified by the client, if applicable. + /// + public string? TokenTypeHint { get; set; } = default!; + + /// + /// Gets or sets the token entry identifier associated with the token, if applicable. + /// + public string? TokenId { get; set; } + + /// + /// Gets or sets the security principal resolved from the token. + /// + public ClaimsPrincipal? Principal { get; set; } + + /// + /// Gets the token types that are considered valid. + /// + public HashSet ValidTokenTypes { get; } = new(StringComparer.OrdinalIgnoreCase); } } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Revocation.cs index e8ecf9d9..2e86f9e7 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Revocation.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Revocation.cs @@ -9,135 +9,134 @@ using System.Collections.Generic; using System.Security.Claims; using OpenIddict.Abstractions; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an event called for each request to the revocation endpoint to give the user code + /// a chance to manually extract the revocation request from the ambient HTTP context. + /// + public class ExtractRevocationRequestContext : BaseValidatingContext { /// - /// Represents an event called for each request to the revocation endpoint to give the user code - /// a chance to manually extract the revocation request from the ambient HTTP context. + /// Creates a new instance of the class. + /// + public ExtractRevocationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request, or null if it wasn't extracted yet. /// - public class ExtractRevocationRequestContext : BaseValidatingContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ExtractRevocationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + get => Transaction.Request; + set => Transaction.Request = value; + } + } - /// - /// Gets or sets the request, or null if it wasn't extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } + /// + /// Represents an event called for each request to the revocation endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateRevocationRequestContext : BaseValidatingClientContext + { + /// + /// Creates a new instance of the class. + /// + public ValidateRevocationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { } /// - /// Represents an event called for each request to the revocation endpoint - /// to determine if the request is valid and should continue to be processed. + /// Gets or sets the request. /// - public class ValidateRevocationRequestContext : BaseValidatingClientContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public ValidateRevocationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + get => Transaction.Request!; + set => Transaction.Request = value; + } - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + /// + /// Gets the optional token_type_hint parameter extracted from the + /// revocation request, or null if it cannot be found. + /// + public string? TokenTypeHint => Request.TokenTypeHint; - /// - /// Gets the optional token_type_hint parameter extracted from the - /// revocation request, or null if it cannot be found. - /// - public string? TokenTypeHint => Request.TokenTypeHint; + /// + /// Gets or sets the security principal extracted from the revoked token, if available. + /// + public ClaimsPrincipal? Principal { get; set; } + } - /// - /// Gets or sets the security principal extracted from the revoked token, if available. - /// - public ClaimsPrincipal? Principal { get; set; } + /// + /// Represents an event called for each validated revocation request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleRevocationRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public HandleRevocationRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { } /// - /// Represents an event called for each validated revocation request - /// to allow the user code to decide how the request should be handled. + /// Gets or sets the request. /// - public class HandleRevocationRequestContext : BaseValidatingContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public HandleRevocationRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + get => Transaction.Request!; + set => Transaction.Request = value; + } - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + /// + /// Gets or sets the security principal extracted from the revoked token. + /// + public ClaimsPrincipal Principal { get; set; } = default!; + } - /// - /// Gets or sets the security principal extracted from the revoked token. - /// - public ClaimsPrincipal Principal { get; set; } = default!; + /// + /// Represents an event called before the revocation response is returned to the caller. + /// + public class ApplyRevocationResponseContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyRevocationResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) + { } /// - /// Represents an event called before the revocation response is returned to the caller. + /// Gets or sets the request, or null if it couldn't be extracted. /// - public class ApplyRevocationResponseContext : BaseRequestContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ApplyRevocationResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } + get => Transaction.Request; + set => Transaction.Request = value; + } - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response.Error; + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; } + + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response.Error; } } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Session.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Session.cs index 889879f6..59304266 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Session.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Session.cs @@ -9,180 +9,179 @@ using System.Collections.Generic; using OpenIddict.Abstractions; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an event called for each request to the logout endpoint to give the user code + /// a chance to manually extract the logout request from the ambient HTTP context. + /// + public class ExtractLogoutRequestContext : BaseValidatingContext { /// - /// Represents an event called for each request to the logout endpoint to give the user code - /// a chance to manually extract the logout request from the ambient HTTP context. + /// Creates a new instance of the class. /// - public class ExtractLogoutRequestContext : BaseValidatingContext + public ExtractLogoutRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ExtractLogoutRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if wasn't extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } } /// - /// Represents an event called for each request to the logout endpoint - /// to determine if the request is valid and should continue to be processed. + /// Gets or sets the request, or null if wasn't extracted yet. /// - public class ValidateLogoutRequestContext : BaseValidatingContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ValidateLogoutRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - // Infer the post_logout_redirect_uri from the value specified by the client application. - => PostLogoutRedirectUri = Request?.PostLogoutRedirectUri; - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + get => Transaction.Request; + set => Transaction.Request = value; + } + } - /// - /// Gets the post_logout_redirect_uri specified by the client application. - /// - public string? PostLogoutRedirectUri { get; private set; } + /// + /// Represents an event called for each request to the logout endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateLogoutRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ValidateLogoutRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + // Infer the post_logout_redirect_uri from the value specified by the client application. + => PostLogoutRedirectUri = Request?.PostLogoutRedirectUri; - /// - /// Populates the property with the specified redirect_uri. - /// - /// The post_logout_redirect_uri to use when redirecting the user agent. - public void SetPostLogoutRedirectUri(string address) - { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0102), nameof(address)); - } - - // Don't allow validation to alter the post_logout_redirect_uri parameter extracted - // from the request if the address was explicitly provided by the client application. - if (!string.IsNullOrEmpty(Request?.PostLogoutRedirectUri) && - !string.Equals(Request.PostLogoutRedirectUri, address, StringComparison.Ordinal)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0103)); - } - - PostLogoutRedirectUri = address; - } + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; } /// - /// Represents an event called for each validated logout request - /// to allow the user code to decide how the request should be handled. + /// Gets the post_logout_redirect_uri specified by the client application. /// - public class HandleLogoutRequestContext : BaseValidatingContext + public string? PostLogoutRedirectUri { get; private set; } + + /// + /// Populates the property with the specified redirect_uri. + /// + /// The post_logout_redirect_uri to use when redirecting the user agent. + public void SetPostLogoutRedirectUri(string address) { - /// - /// Creates a new instance of the class. - /// - public HandleLogoutRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) + if (string.IsNullOrEmpty(address)) { + throw new ArgumentException(SR.GetResourceString(SR.ID0102), nameof(address)); } - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request + // Don't allow validation to alter the post_logout_redirect_uri parameter extracted + // from the request if the address was explicitly provided by the client application. + if (!string.IsNullOrEmpty(Request?.PostLogoutRedirectUri) && + !string.Equals(Request.PostLogoutRedirectUri, address, StringComparison.Ordinal)) { - get => Transaction.Request!; - set => Transaction.Request = value; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0103)); } - /// - /// Gets a boolean indicating whether a sign-out should be triggered. - /// - public bool IsSignOutTriggered { get; private set; } - - /// - /// Gets the additional parameters returned to the client application. - /// - public Dictionary Parameters { get; private set; } - = new(StringComparer.Ordinal); - - /// - /// Allows OpenIddict to return a sign-out response. - /// - public void SignOut() => IsSignOutTriggered = true; - - /// - /// Allows OpenIddict to return a sign-out response. - /// - /// The additional parameters returned to the client application. - public void SignOut(IDictionary parameters) - { - IsSignOutTriggered = true; - Parameters = new(parameters, StringComparer.Ordinal); - } + PostLogoutRedirectUri = address; } + } + /// + /// Represents an event called for each validated logout request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleLogoutRequestContext : BaseValidatingContext + { /// - /// Represents an event called before the logout response is returned to the caller. + /// Creates a new instance of the class. /// - public class ApplyLogoutResponseContext : BaseRequestContext + public HandleLogoutRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ApplyLogoutResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + } - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } + /// + /// Gets a boolean indicating whether a sign-out should be triggered. + /// + public bool IsSignOutTriggered { get; private set; } + + /// + /// Gets the additional parameters returned to the client application. + /// + public Dictionary Parameters { get; private set; } + = new(StringComparer.Ordinal); + + /// + /// Allows OpenIddict to return a sign-out response. + /// + public void SignOut() => IsSignOutTriggered = true; - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response.Error; - - /// - /// Gets or sets the callback URL the user agent will be redirected to, if applicable. - /// Note: manually changing the value of this property is generally not recommended - /// and extreme caution must be taken to ensure the user agent is not redirected to - /// an untrusted address, which would result in an "open redirection" vulnerability. - /// - public string? PostLogoutRedirectUri { get; set; } + /// + /// Allows OpenIddict to return a sign-out response. + /// + /// The additional parameters returned to the client application. + public void SignOut(IDictionary parameters) + { + IsSignOutTriggered = true; + Parameters = new(parameters, StringComparer.Ordinal); } } + + /// + /// Represents an event called before the logout response is returned to the caller. + /// + public class ApplyLogoutResponseContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyLogoutResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request, or null if it couldn't be extracted. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response.Error; + + /// + /// Gets or sets the callback URL the user agent will be redirected to, if applicable. + /// Note: manually changing the value of this property is generally not recommended + /// and extreme caution must be taken to ensure the user agent is not redirected to + /// an untrusted address, which would result in an "open redirection" vulnerability. + /// + public string? PostLogoutRedirectUri { get; set; } + } } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Userinfo.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Userinfo.cs index d7f4b602..35a3c028 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Userinfo.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Userinfo.cs @@ -10,222 +10,221 @@ using System.Security.Claims; using System.Text.Json; using OpenIddict.Abstractions; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an event called for each request to the userinfo endpoint to give the user code + /// a chance to manually extract the userinfo request from the ambient HTTP context. + /// + public class ExtractUserinfoRequestContext : BaseValidatingContext { /// - /// Represents an event called for each request to the userinfo endpoint to give the user code - /// a chance to manually extract the userinfo request from the ambient HTTP context. + /// Creates a new instance of the class. /// - public class ExtractUserinfoRequestContext : BaseValidatingContext + public ExtractUserinfoRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ExtractUserinfoRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it wasn't extracted yet. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } } /// - /// Represents an event called for each request to the userinfo endpoint - /// to determine if the request is valid and should continue to be processed. + /// Gets or sets the request, or null if it wasn't extracted yet. /// - public class ValidateUserinfoRequestContext : BaseValidatingContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ValidateUserinfoRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the security principal extracted from the access token, if available. - /// - public ClaimsPrincipal? Principal { get; set; } + get => Transaction.Request; + set => Transaction.Request = value; } + } + /// + /// Represents an event called for each request to the userinfo endpoint + /// to determine if the request is valid and should continue to be processed. + /// + public class ValidateUserinfoRequestContext : BaseValidatingContext + { /// - /// Represents an event called for each validated userinfo request - /// to allow the user code to decide how the request should be handled. + /// Creates a new instance of the class. /// - public class HandleUserinfoRequestContext : BaseValidatingContext + public ValidateUserinfoRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public HandleUserinfoRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the security principal extracted from the access token. - /// - public ClaimsPrincipal Principal { get; set; } = default!; - - /// - /// Gets the additional claims returned to the client application. - /// - public Dictionary Claims { get; } = new(StringComparer.Ordinal); - - /// - /// Gets or sets the value used for the "address" claim. - /// Note: this value should only be populated if the "address" - /// scope was requested and accepted by the resource owner. - /// - public JsonElement Address { get; set; } - - /// - /// Gets or sets the values used for the "aud" claim. - /// - public HashSet Audiences { get; } = new HashSet(StringComparer.Ordinal); - - /// - /// Gets or sets the value used for the "birthdate" claim. - /// Note: this value should only be populated if the "profile" - /// scope was requested and accepted by the resource owner. - /// - public string? BirthDate { get; set; } - - /// - /// Gets or sets the value used for the "email" claim. - /// Note: this value should only be populated if the "email" - /// scope was requested and accepted by the resource owner. - /// - public string? Email { get; set; } - - /// - /// Gets or sets the value used for the "email_verified" claim. - /// Note: this value should only be populated if the "email" - /// scope was requested and accepted by the resource owner. - /// - public bool? EmailVerified { get; set; } - - /// - /// Gets or sets the value used for the "family_name" claim. - /// Note: this value should only be populated if the "profile" - /// scope was requested and accepted by the resource owner. - /// - public string? FamilyName { get; set; } - - /// - /// Gets or sets the value used for the "given_name" claim. - /// Note: this value should only be populated if the "profile" - /// scope was requested and accepted by the resource owner. - /// - public string? GivenName { get; set; } - - /// - /// Gets or sets the value used for the "phone_number" claim. - /// Note: this value should only be populated if the "phone" - /// scope was requested and accepted by the resource owner. - /// - public string? PhoneNumber { get; set; } - - /// - /// Gets or sets the value used for the "phone_number_verified" claim. - /// Note: this value should only be populated if the "phone" - /// scope was requested and accepted by the resource owner. - /// - public bool? PhoneNumberVerified { get; set; } - - /// - /// Gets or sets the value used for the "preferred_username" claim. - /// Note: this value should only be populated if the "profile" - /// scope was requested and accepted by the resource owner. - /// - public string? PreferredUsername { get; set; } - - /// - /// Gets or sets the value used for the "profile" claim. - /// Note: this value should only be populated if the "profile" - /// scope was requested and accepted by the resource owner. - /// - public string? Profile { get; set; } - - /// - /// Gets or sets the unique value - /// used for the mandatory "sub" claim. - /// - public string? Subject { get; set; } - - /// - /// Gets or sets the value used for the "website" claim. - /// Note: this value should only be populated if the "profile" - /// scope was requested and accepted by the resource owner. - /// - public string? Website { get; set; } } /// - /// Represents an event called before the userinfo response is returned to the caller. + /// Gets or sets the request. /// - public class ApplyUserinfoResponseContext : BaseRequestContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public ApplyUserinfoResponseContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the error code returned to the client application. - /// When the response indicates a successful response, - /// this property returns null. - /// - public string? Error => Response.Error; + get => Transaction.Request!; + set => Transaction.Request = value; } + + /// + /// Gets or sets the security principal extracted from the access token, if available. + /// + public ClaimsPrincipal? Principal { get; set; } + } + + /// + /// Represents an event called for each validated userinfo request + /// to allow the user code to decide how the request should be handled. + /// + public class HandleUserinfoRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public HandleUserinfoRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the security principal extracted from the access token. + /// + public ClaimsPrincipal Principal { get; set; } = default!; + + /// + /// Gets the additional claims returned to the client application. + /// + public Dictionary Claims { get; } = new(StringComparer.Ordinal); + + /// + /// Gets or sets the value used for the "address" claim. + /// Note: this value should only be populated if the "address" + /// scope was requested and accepted by the resource owner. + /// + public JsonElement Address { get; set; } + + /// + /// Gets or sets the values used for the "aud" claim. + /// + public HashSet Audiences { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets or sets the value used for the "birthdate" claim. + /// Note: this value should only be populated if the "profile" + /// scope was requested and accepted by the resource owner. + /// + public string? BirthDate { get; set; } + + /// + /// Gets or sets the value used for the "email" claim. + /// Note: this value should only be populated if the "email" + /// scope was requested and accepted by the resource owner. + /// + public string? Email { get; set; } + + /// + /// Gets or sets the value used for the "email_verified" claim. + /// Note: this value should only be populated if the "email" + /// scope was requested and accepted by the resource owner. + /// + public bool? EmailVerified { get; set; } + + /// + /// Gets or sets the value used for the "family_name" claim. + /// Note: this value should only be populated if the "profile" + /// scope was requested and accepted by the resource owner. + /// + public string? FamilyName { get; set; } + + /// + /// Gets or sets the value used for the "given_name" claim. + /// Note: this value should only be populated if the "profile" + /// scope was requested and accepted by the resource owner. + /// + public string? GivenName { get; set; } + + /// + /// Gets or sets the value used for the "phone_number" claim. + /// Note: this value should only be populated if the "phone" + /// scope was requested and accepted by the resource owner. + /// + public string? PhoneNumber { get; set; } + + /// + /// Gets or sets the value used for the "phone_number_verified" claim. + /// Note: this value should only be populated if the "phone" + /// scope was requested and accepted by the resource owner. + /// + public bool? PhoneNumberVerified { get; set; } + + /// + /// Gets or sets the value used for the "preferred_username" claim. + /// Note: this value should only be populated if the "profile" + /// scope was requested and accepted by the resource owner. + /// + public string? PreferredUsername { get; set; } + + /// + /// Gets or sets the value used for the "profile" claim. + /// Note: this value should only be populated if the "profile" + /// scope was requested and accepted by the resource owner. + /// + public string? Profile { get; set; } + + /// + /// Gets or sets the unique value + /// used for the mandatory "sub" claim. + /// + public string? Subject { get; set; } + + /// + /// Gets or sets the value used for the "website" claim. + /// Note: this value should only be populated if the "profile" + /// scope was requested and accepted by the resource owner. + /// + public string? Website { get; set; } + } + + /// + /// Represents an event called before the userinfo response is returned to the caller. + /// + public class ApplyUserinfoResponseContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyUserinfoResponseContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request, or null if it couldn't be extracted. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the error code returned to the client application. + /// When the response indicates a successful response, + /// this property returns null. + /// + public string? Error => Response.Error; } } diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.cs b/src/OpenIddict.Server/OpenIddictServerEvents.cs index 0ea09025..ddf12dc0 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.cs @@ -11,758 +11,757 @@ using System.Security.Claims; using Microsoft.Extensions.Logging; using OpenIddict.Abstractions; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerEvents { - public static partial class OpenIddictServerEvents + /// + /// Represents an abstract base class used for certain event contexts. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class BaseContext { /// - /// Represents an abstract base class used for certain event contexts. + /// Creates a new instance of the class. + /// + protected BaseContext(OpenIddictServerTransaction transaction) + => Transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); + + /// + /// Gets the environment associated with the current request being processed. + /// + public OpenIddictServerTransaction Transaction { get; } + + /// + /// Gets or sets the issuer address associated with the current transaction, if available. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class BaseContext + public Uri? Issuer { - /// - /// Creates a new instance of the class. - /// - protected BaseContext(OpenIddictServerTransaction transaction) - => Transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); - - /// - /// Gets the environment associated with the current request being processed. - /// - public OpenIddictServerTransaction Transaction { get; } - - /// - /// Gets or sets the issuer address associated with the current transaction, if available. - /// - public Uri? Issuer - { - get => Transaction.Issuer; - set => Transaction.Issuer = value; - } - - /// - /// Gets or sets the endpoint type that handled the request, if applicable. - /// - public OpenIddictServerEndpointType EndpointType - { - get => Transaction.EndpointType; - set => Transaction.EndpointType = value; - } - - /// - /// Gets the logger responsible of logging processed operations. - /// - public ILogger Logger => Transaction.Logger; - - /// - /// Gets the OpenIddict server options. - /// - public OpenIddictServerOptions Options => Transaction.Options; + get => Transaction.Issuer; + set => Transaction.Issuer = value; } /// - /// Represents an abstract base class used for certain event contexts. + /// Gets or sets the endpoint type that handled the request, if applicable. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class BaseRequestContext : BaseContext + public OpenIddictServerEndpointType EndpointType { - /// - /// Creates a new instance of the class. - /// - protected BaseRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets a boolean indicating whether the request was fully handled. - /// - public bool IsRequestHandled { get; private set; } - - /// - /// Gets a boolean indicating whether the request processing was skipped. - /// - public bool IsRequestSkipped { get; private set; } - - /// - /// Marks the request as fully handled. Once declared handled, - /// a request shouldn't be processed further by the underlying host. - /// - public void HandleRequest() => IsRequestHandled = true; - - /// - /// Marks the request as skipped. Once declared skipped, a request - /// shouldn't be processed further by OpenIddict but should be allowed - /// to go through the next components in the processing pipeline - /// (if this pattern is supported by the underlying host). - /// - public void SkipRequest() => IsRequestSkipped = true; + get => Transaction.EndpointType; + set => Transaction.EndpointType = value; } /// - /// Represents an abstract base class used for certain event contexts. + /// Gets the logger responsible of logging processed operations. + /// + public ILogger Logger => Transaction.Logger; + + /// + /// Gets the OpenIddict server options. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class BaseValidatingClientContext : BaseValidatingContext + public OpenIddictServerOptions Options => Transaction.Options; + } + + /// + /// Represents an abstract base class used for certain event contexts. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class BaseRequestContext : BaseContext + { + /// + /// Creates a new instance of the class. + /// + protected BaseRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - protected BaseValidatingClientContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets the "client_id" parameter for the current request. - /// The authorization server application is responsible for - /// validating this value to ensure it identifies a registered client. - /// - public string? ClientId => Transaction.Request?.ClientId; - - /// - /// Gets the "client_secret" parameter for the current request. - /// The authorization server application is responsible for - /// validating this value to ensure it identifies a registered client. - /// - public string? ClientSecret => Transaction.Request?.ClientSecret; } /// - /// Represents an abstract base class used for certain event contexts. + /// Gets a boolean indicating whether the request was fully handled. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class BaseValidatingContext : BaseRequestContext + public bool IsRequestHandled { get; private set; } + + /// + /// Gets a boolean indicating whether the request processing was skipped. + /// + public bool IsRequestSkipped { get; private set; } + + /// + /// Marks the request as fully handled. Once declared handled, + /// a request shouldn't be processed further by the underlying host. + /// + public void HandleRequest() => IsRequestHandled = true; + + /// + /// Marks the request as skipped. Once declared skipped, a request + /// shouldn't be processed further by OpenIddict but should be allowed + /// to go through the next components in the processing pipeline + /// (if this pattern is supported by the underlying host). + /// + public void SkipRequest() => IsRequestSkipped = true; + } + + /// + /// Represents an abstract base class used for certain event contexts. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class BaseValidatingClientContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + protected BaseValidatingClientContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - protected BaseValidatingContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets a boolean indicating whether the request will be rejected. - /// - public bool IsRejected { get; protected set; } - - /// - /// Gets or sets the "error" parameter returned to the client application. - /// - public string? Error { get; private set; } - - /// - /// Gets or sets the "error_description" parameter returned to the client application. - /// - public string? ErrorDescription { get; private set; } - - /// - /// Gets or sets the "error_uri" parameter returned to the client application. - /// - public string? ErrorUri { get; private set; } - - /// - /// Rejects the request. - /// - /// The "error" parameter returned to the client application. - /// The "error_description" parameter returned to the client application. - /// The "error_uri" parameter returned to the client application. - public virtual void Reject(string? error = null, string? description = null, string? uri = null) - { - Error = error; - ErrorDescription = description; - ErrorUri = uri; - - IsRejected = true; - } } /// - /// Represents an abstract base class used for certain event contexts. + /// Gets the "client_id" parameter for the current request. + /// The authorization server application is responsible for + /// validating this value to ensure it identifies a registered client. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class BaseValidatingTicketContext : BaseValidatingContext + public string? ClientId => Transaction.Request?.ClientId; + + /// + /// Gets the "client_secret" parameter for the current request. + /// The authorization server application is responsible for + /// validating this value to ensure it identifies a registered client. + /// + public string? ClientSecret => Transaction.Request?.ClientSecret; + } + + /// + /// Represents an abstract base class used for certain event contexts. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class BaseValidatingContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + protected BaseValidatingContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - protected BaseValidatingTicketContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the security principal. - /// - public ClaimsPrincipal? Principal { get; set; } - - /// - /// Gets the client identifier, or null if the client application is unknown. - /// - public string? ClientId => Transaction.Request?.ClientId; } /// - /// Represents an event called when processing an incoming request. + /// Gets a boolean indicating whether the request will be rejected. /// - public class ProcessRequestContext : BaseValidatingContext + public bool IsRejected { get; protected set; } + + /// + /// Gets or sets the "error" parameter returned to the client application. + /// + public string? Error { get; private set; } + + /// + /// Gets or sets the "error_description" parameter returned to the client application. + /// + public string? ErrorDescription { get; private set; } + + /// + /// Gets or sets the "error_uri" parameter returned to the client application. + /// + public string? ErrorUri { get; private set; } + + /// + /// Rejects the request. + /// + /// The "error" parameter returned to the client application. + /// The "error_description" parameter returned to the client application. + /// The "error_uri" parameter returned to the client application. + public virtual void Reject(string? error = null, string? description = null, string? uri = null) { - /// - /// Creates a new instance of the class. - /// - public ProcessRequestContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } + Error = error; + ErrorDescription = description; + ErrorUri = uri; + + IsRejected = true; } + } + /// + /// Represents an abstract base class used for certain event contexts. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class BaseValidatingTicketContext : BaseValidatingContext + { /// - /// Represents an event called when processing an errored response. + /// Creates a new instance of the class. /// - public class ProcessErrorContext : BaseRequestContext + protected BaseValidatingTicketContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ProcessErrorContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets or sets the error returned to the caller. - /// - public string? Error { get; set; } - - /// - /// Gets or sets the error description returned to the caller. - /// - public string? ErrorDescription { get; set; } - - /// - /// Gets or sets the error URL returned to the caller. - /// - public string? ErrorUri { get; set; } - - /// - /// Gets the additional parameters returned to the caller. - /// - public Dictionary Parameters { get; } = new(StringComparer.Ordinal); } /// - /// Represents an event called when processing an authentication operation. + /// Gets or sets the security principal. + /// + public ClaimsPrincipal? Principal { get; set; } + + /// + /// Gets the client identifier, or null if the client application is unknown. + /// + public string? ClientId => Transaction.Request?.ClientId; + } + + /// + /// Represents an event called when processing an incoming request. + /// + public class ProcessRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. /// - public class ProcessAuthenticationContext : BaseValidatingContext + public ProcessRequestContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ProcessAuthenticationContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets a boolean indicating whether an access token - /// must be resolved for the authentication to considered valid. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool RequireAccessToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether an authorization code - /// must be resolved for the authentication to considered valid. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool RequireAuthorizationCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether a device code - /// must be resolved for the authentication to considered valid. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool RequireDeviceCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether a generic token - /// must be resolved for the authentication to considered valid. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool RequireGenericToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether an identity token - /// must be resolved for the authentication to considered valid. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool RequireIdentityToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether a refresh token - /// must be resolved for the authentication to considered valid. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool RequireRefreshToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether a user code - /// must be resolved for the authentication to considered valid. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool RequireUserCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether an access token - /// should be extracted from the current context and validated. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool ValidateAccessToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether an authorization code - /// should be extracted from the current context and validated. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool ValidateAuthorizationCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether a device code - /// should be extracted from the current context and validated. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool ValidateDeviceCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether a generic token - /// should be extracted from the current context and validated. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool ValidateGenericToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether an identity token - /// should be extracted from the current context and validated. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool ValidateIdentityToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether a refresh token - /// should be extracted from the current context and validated. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool ValidateRefreshToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether a user code - /// should be extracted from the current context and validated. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool ValidateUserCode { get; set; } - - /// - /// Gets or sets the access token to validate, if applicable. - /// - public string? AccessToken { get; set; } - - /// - /// Gets or sets the principal extracted from the access token, if applicable. - /// - public ClaimsPrincipal? AccessTokenPrincipal { get; set; } - - /// - /// Gets or sets the authorization code to validate, if applicable. - /// - public string? AuthorizationCode { get; set; } - - /// - /// Gets or sets the principal extracted from the authorization code, if applicable. - /// - public ClaimsPrincipal? AuthorizationCodePrincipal { get; set; } - - /// - /// Gets or sets the device code to validate, if applicable. - /// - public string? DeviceCode { get; set; } - - /// - /// Gets or sets the principal extracted from the device code, if applicable. - /// - public ClaimsPrincipal? DeviceCodePrincipal { get; set; } - - /// - /// Gets or sets the generic token to validate, if applicable. - /// - public string? GenericToken { get; set; } - - /// - /// Gets or sets the optional hint indicating the type of the generic token, if applicable. - /// - public string? GenericTokenTypeHint { get; set; } - - /// - /// Gets or sets the principal extracted from the generic token, if applicable. - /// - public ClaimsPrincipal? GenericTokenPrincipal { get; set; } - - /// - /// Gets or sets the identity token to validate, if applicable. - /// - public string? IdentityToken { get; set; } - - /// - /// Gets or sets the principal extracted from the identity token, if applicable. - /// - public ClaimsPrincipal? IdentityTokenPrincipal { get; set; } - - /// - /// Gets or sets the refresh token to validate, if applicable. - /// - public string? RefreshToken { get; set; } - - /// - /// Gets or sets the principal extracted from the refresh token, if applicable. - /// - public ClaimsPrincipal? RefreshTokenPrincipal { get; set; } - - /// - /// Gets or sets the user code to validate, if applicable. - /// - public string? UserCode { get; set; } - - /// - /// Gets or sets the principal extracted from the user code, if applicable. - /// - public ClaimsPrincipal? UserCodePrincipal { get; set; } } + } + /// + /// Represents an event called when processing an errored response. + /// + public class ProcessErrorContext : BaseRequestContext + { /// - /// Represents an event called when processing a challenge response. + /// Creates a new instance of the class. /// - public class ProcessChallengeContext : BaseValidatingContext + public ProcessErrorContext(OpenIddictServerTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ProcessChallengeContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the additional parameters returned to caller. - /// - public Dictionary Parameters { get; } = new(StringComparer.Ordinal); } /// - /// Represents an event called when processing a sign-in response. + /// Gets or sets the request or null if it couldn't be extracted. /// - public class ProcessSignInContext : BaseValidatingTicketContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ProcessSignInContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the additional parameters returned to caller. - /// - public Dictionary Parameters { get; } = new(StringComparer.Ordinal); - - /// - /// Gets or sets a boolean indicating whether an access token - /// should be generated (and optionally returned to the client). - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool GenerateAccessToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether an authorization code - /// should be generated (and optionally returned to the client). - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool GenerateAuthorizationCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether a device code - /// should be generated (and optionally returned to the client). - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool GenerateDeviceCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether an identity token - /// should be generated (and optionally returned to the client). - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool GenerateIdentityToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether a refresh token - /// should be generated (and optionally returned to the client). - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool GenerateRefreshToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether a user code - /// should be generated (and optionally returned to the client). - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool GenerateUserCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether the generated access token - /// should be returned to the client application as part of the response. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool IncludeAccessToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether the generated authorization code - /// should be returned to the client application as part of the response. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool IncludeAuthorizationCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether the generated device code - /// should be returned to the client application as part of the response. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool IncludeDeviceCode { get; set; } - - /// - /// Gets or sets a boolean indicating whether the generated identity token - /// should be returned to the client application as part of the response. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool IncludeIdentityToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether the generated refresh token - /// should be returned to the client application as part of the response. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool IncludeRefreshToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether the generated user code - /// should be returned to the client application as part of the response. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool IncludeUserCode { get; set; } - - /// - /// Gets or sets the generated access token, if applicable. - /// The access token will only be returned if - /// is set to true. - /// - public string? AccessToken { get; set; } - - /// - /// Gets or sets the principal containing the claims that - /// will be used to create the access token, if applicable. - /// - public ClaimsPrincipal? AccessTokenPrincipal { get; set; } - - /// - /// Gets or sets the generated authorization code, if applicable. - /// The authorization code will only be returned if - /// is set to true. - /// - public string? AuthorizationCode { get; set; } - - /// - /// Gets or sets the principal containing the claims that - /// will be used to create the authorization code, if applicable. - /// - public ClaimsPrincipal? AuthorizationCodePrincipal { get; set; } - - /// - /// Gets or sets the generated device code, if applicable. - /// The device code will only be returned if - /// is set to true. - /// - public string? DeviceCode { get; set; } - - /// - /// Gets or sets the principal containing the claims that - /// will be used to create the device code, if applicable. - /// - public ClaimsPrincipal? DeviceCodePrincipal { get; set; } - - /// - /// Gets or sets the generated identity token, if applicable. - /// The identity token will only be returned if - /// is set to true. - /// - public string? IdentityToken { get; set; } - - /// - /// Gets or sets the principal containing the claims that - /// will be used to create the identity token, if applicable. - /// - public ClaimsPrincipal? IdentityTokenPrincipal { get; set; } - - /// - /// Gets or sets the generated refresh token, if applicable. - /// The refresh token will only be returned if - /// is set to true. - /// - public string? RefreshToken { get; set; } - - /// - /// Gets or sets the principal containing the claims that - /// will be used to create the refresh token, if applicable. - /// - public ClaimsPrincipal? RefreshTokenPrincipal { get; set; } - - /// - /// Gets or sets the generated user code, if applicable. - /// The user code will only be returned if - /// is set to true. - /// - public string? UserCode { get; set; } - - /// - /// Gets or sets the principal containing the claims that - /// will be used to create the user code, if applicable. - /// - public ClaimsPrincipal? UserCodePrincipal { get; set; } + get => Transaction.Request; + set => Transaction.Request = value; } /// - /// Represents an event called when processing a sign-out response. + /// Gets or sets the response. /// - public class ProcessSignOutContext : BaseValidatingContext + public OpenIddictResponse Response { - /// - /// Creates a new instance of the class. - /// - public ProcessSignOutContext(OpenIddictServerTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the additional parameters returned to caller. - /// - public Dictionary Parameters { get; } = new(StringComparer.Ordinal); + get => Transaction.Response!; + set => Transaction.Response = value; } + + /// + /// Gets or sets the error returned to the caller. + /// + public string? Error { get; set; } + + /// + /// Gets or sets the error description returned to the caller. + /// + public string? ErrorDescription { get; set; } + + /// + /// Gets or sets the error URL returned to the caller. + /// + public string? ErrorUri { get; set; } + + /// + /// Gets the additional parameters returned to the caller. + /// + public Dictionary Parameters { get; } = new(StringComparer.Ordinal); + } + + /// + /// Represents an event called when processing an authentication operation. + /// + public class ProcessAuthenticationContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ProcessAuthenticationContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets a boolean indicating whether an access token + /// must be resolved for the authentication to considered valid. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool RequireAccessToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether an authorization code + /// must be resolved for the authentication to considered valid. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool RequireAuthorizationCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether a device code + /// must be resolved for the authentication to considered valid. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool RequireDeviceCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether a generic token + /// must be resolved for the authentication to considered valid. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool RequireGenericToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether an identity token + /// must be resolved for the authentication to considered valid. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool RequireIdentityToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether a refresh token + /// must be resolved for the authentication to considered valid. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool RequireRefreshToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether a user code + /// must be resolved for the authentication to considered valid. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool RequireUserCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether an access token + /// should be extracted from the current context and validated. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool ValidateAccessToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether an authorization code + /// should be extracted from the current context and validated. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool ValidateAuthorizationCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether a device code + /// should be extracted from the current context and validated. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool ValidateDeviceCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether a generic token + /// should be extracted from the current context and validated. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool ValidateGenericToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether an identity token + /// should be extracted from the current context and validated. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool ValidateIdentityToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether a refresh token + /// should be extracted from the current context and validated. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool ValidateRefreshToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether a user code + /// should be extracted from the current context and validated. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool ValidateUserCode { get; set; } + + /// + /// Gets or sets the access token to validate, if applicable. + /// + public string? AccessToken { get; set; } + + /// + /// Gets or sets the principal extracted from the access token, if applicable. + /// + public ClaimsPrincipal? AccessTokenPrincipal { get; set; } + + /// + /// Gets or sets the authorization code to validate, if applicable. + /// + public string? AuthorizationCode { get; set; } + + /// + /// Gets or sets the principal extracted from the authorization code, if applicable. + /// + public ClaimsPrincipal? AuthorizationCodePrincipal { get; set; } + + /// + /// Gets or sets the device code to validate, if applicable. + /// + public string? DeviceCode { get; set; } + + /// + /// Gets or sets the principal extracted from the device code, if applicable. + /// + public ClaimsPrincipal? DeviceCodePrincipal { get; set; } + + /// + /// Gets or sets the generic token to validate, if applicable. + /// + public string? GenericToken { get; set; } + + /// + /// Gets or sets the optional hint indicating the type of the generic token, if applicable. + /// + public string? GenericTokenTypeHint { get; set; } + + /// + /// Gets or sets the principal extracted from the generic token, if applicable. + /// + public ClaimsPrincipal? GenericTokenPrincipal { get; set; } + + /// + /// Gets or sets the identity token to validate, if applicable. + /// + public string? IdentityToken { get; set; } + + /// + /// Gets or sets the principal extracted from the identity token, if applicable. + /// + public ClaimsPrincipal? IdentityTokenPrincipal { get; set; } + + /// + /// Gets or sets the refresh token to validate, if applicable. + /// + public string? RefreshToken { get; set; } + + /// + /// Gets or sets the principal extracted from the refresh token, if applicable. + /// + public ClaimsPrincipal? RefreshTokenPrincipal { get; set; } + + /// + /// Gets or sets the user code to validate, if applicable. + /// + public string? UserCode { get; set; } + + /// + /// Gets or sets the principal extracted from the user code, if applicable. + /// + public ClaimsPrincipal? UserCodePrincipal { get; set; } + } + + /// + /// Represents an event called when processing a challenge response. + /// + public class ProcessChallengeContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ProcessChallengeContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the additional parameters returned to caller. + /// + public Dictionary Parameters { get; } = new(StringComparer.Ordinal); + } + + /// + /// Represents an event called when processing a sign-in response. + /// + public class ProcessSignInContext : BaseValidatingTicketContext + { + /// + /// Creates a new instance of the class. + /// + public ProcessSignInContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the additional parameters returned to caller. + /// + public Dictionary Parameters { get; } = new(StringComparer.Ordinal); + + /// + /// Gets or sets a boolean indicating whether an access token + /// should be generated (and optionally returned to the client). + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool GenerateAccessToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether an authorization code + /// should be generated (and optionally returned to the client). + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool GenerateAuthorizationCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether a device code + /// should be generated (and optionally returned to the client). + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool GenerateDeviceCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether an identity token + /// should be generated (and optionally returned to the client). + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool GenerateIdentityToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether a refresh token + /// should be generated (and optionally returned to the client). + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool GenerateRefreshToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether a user code + /// should be generated (and optionally returned to the client). + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool GenerateUserCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether the generated access token + /// should be returned to the client application as part of the response. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool IncludeAccessToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether the generated authorization code + /// should be returned to the client application as part of the response. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool IncludeAuthorizationCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether the generated device code + /// should be returned to the client application as part of the response. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool IncludeDeviceCode { get; set; } + + /// + /// Gets or sets a boolean indicating whether the generated identity token + /// should be returned to the client application as part of the response. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool IncludeIdentityToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether the generated refresh token + /// should be returned to the client application as part of the response. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool IncludeRefreshToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether the generated user code + /// should be returned to the client application as part of the response. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool IncludeUserCode { get; set; } + + /// + /// Gets or sets the generated access token, if applicable. + /// The access token will only be returned if + /// is set to true. + /// + public string? AccessToken { get; set; } + + /// + /// Gets or sets the principal containing the claims that + /// will be used to create the access token, if applicable. + /// + public ClaimsPrincipal? AccessTokenPrincipal { get; set; } + + /// + /// Gets or sets the generated authorization code, if applicable. + /// The authorization code will only be returned if + /// is set to true. + /// + public string? AuthorizationCode { get; set; } + + /// + /// Gets or sets the principal containing the claims that + /// will be used to create the authorization code, if applicable. + /// + public ClaimsPrincipal? AuthorizationCodePrincipal { get; set; } + + /// + /// Gets or sets the generated device code, if applicable. + /// The device code will only be returned if + /// is set to true. + /// + public string? DeviceCode { get; set; } + + /// + /// Gets or sets the principal containing the claims that + /// will be used to create the device code, if applicable. + /// + public ClaimsPrincipal? DeviceCodePrincipal { get; set; } + + /// + /// Gets or sets the generated identity token, if applicable. + /// The identity token will only be returned if + /// is set to true. + /// + public string? IdentityToken { get; set; } + + /// + /// Gets or sets the principal containing the claims that + /// will be used to create the identity token, if applicable. + /// + public ClaimsPrincipal? IdentityTokenPrincipal { get; set; } + + /// + /// Gets or sets the generated refresh token, if applicable. + /// The refresh token will only be returned if + /// is set to true. + /// + public string? RefreshToken { get; set; } + + /// + /// Gets or sets the principal containing the claims that + /// will be used to create the refresh token, if applicable. + /// + public ClaimsPrincipal? RefreshTokenPrincipal { get; set; } + + /// + /// Gets or sets the generated user code, if applicable. + /// The user code will only be returned if + /// is set to true. + /// + public string? UserCode { get; set; } + + /// + /// Gets or sets the principal containing the claims that + /// will be used to create the user code, if applicable. + /// + public ClaimsPrincipal? UserCodePrincipal { get; set; } + } + + /// + /// Represents an event called when processing a sign-out response. + /// + public class ProcessSignOutContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ProcessSignOutContext(OpenIddictServerTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the additional parameters returned to caller. + /// + public Dictionary Parameters { get; } = new(StringComparer.Ordinal); } } diff --git a/src/OpenIddict.Server/OpenIddictServerExtensions.cs b/src/OpenIddict.Server/OpenIddictServerExtensions.cs index a6038ff9..a5667bb3 100644 --- a/src/OpenIddict.Server/OpenIddictServerExtensions.cs +++ b/src/OpenIddict.Server/OpenIddictServerExtensions.cs @@ -11,107 +11,106 @@ using OpenIddict.Server; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using static OpenIddict.Server.OpenIddictServerHandlers; -namespace Microsoft.Extensions.DependencyInjection -{ - using Microsoft.Extensions.Options; +namespace Microsoft.Extensions.DependencyInjection; + +using Microsoft.Extensions.Options; +/// +/// Exposes extensions allowing to register the OpenIddict server services. +/// +public static class OpenIddictServerExtensions +{ /// - /// Exposes extensions allowing to register the OpenIddict server services. + /// Registers the OpenIddict token server services in the DI container. /// - public static class OpenIddictServerExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictServerBuilder AddServer(this OpenIddictBuilder builder) { - /// - /// Registers the OpenIddict token server services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictServerBuilder AddServer(this OpenIddictBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddLogging(); - builder.Services.AddOptions(); + builder.Services.AddLogging(); + builder.Services.AddOptions(); - builder.Services.TryAddScoped(); - builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); - // Register the built-in server event handlers used by the OpenIddict server components. - // Note: the order used here is not important, as the actual order is set in the options. - builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); + // Register the built-in server event handlers used by the OpenIddict server components. + // Note: the order used here is not important, as the actual order is set in the options. + builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); - // Register the built-in filters used by the default OpenIddict server event handlers. - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); + // Register the built-in filters used by the default OpenIddict server event handlers. + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); - // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< - IPostConfigureOptions, OpenIddictServerConfiguration>()); + // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< + IPostConfigureOptions, OpenIddictServerConfiguration>()); - return new OpenIddictServerBuilder(builder.Services); - } + return new OpenIddictServerBuilder(builder.Services); + } - /// - /// Registers the OpenIddict token server services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the server services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictBuilder AddServer(this OpenIddictBuilder builder, Action configuration) + /// + /// Registers the OpenIddict token server services in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the server services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictBuilder AddServer(this OpenIddictBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.AddServer()); + configuration(builder.AddServer()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Server/OpenIddictServerFactory.cs b/src/OpenIddict.Server/OpenIddictServerFactory.cs index bd73ef98..4d0bffd8 100644 --- a/src/OpenIddict.Server/OpenIddictServerFactory.cs +++ b/src/OpenIddict.Server/OpenIddictServerFactory.cs @@ -8,30 +8,29 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public class OpenIddictServerFactory : IOpenIddictServerFactory { - public class OpenIddictServerFactory : IOpenIddictServerFactory + private readonly ILogger _logger; + private readonly IOptionsMonitor _options; + + /// + /// Creates a new instance of the class. + /// + public OpenIddictServerFactory( + ILogger logger, + IOptionsMonitor options) { - private readonly ILogger _logger; - private readonly IOptionsMonitor _options; + _logger = logger; + _options = options; + } - /// - /// Creates a new instance of the class. - /// - public OpenIddictServerFactory( - ILogger logger, - IOptionsMonitor options) + public ValueTask CreateTransactionAsync() + => new ValueTask(new OpenIddictServerTransaction { - _logger = logger; - _options = options; - } - - public ValueTask CreateTransactionAsync() - => new ValueTask(new OpenIddictServerTransaction - { - Issuer = _options.CurrentValue.Issuer, - Logger = _logger, - Options = _options.CurrentValue - }); - } + Issuer = _options.CurrentValue.Issuer, + Logger = _logger, + Options = _options.CurrentValue + }); } diff --git a/src/OpenIddict.Server/OpenIddictServerHandler.cs b/src/OpenIddict.Server/OpenIddictServerHandler.cs index 68f1244d..93b43e26 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandler.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandler.cs @@ -8,31 +8,30 @@ using System; using System.Threading.Tasks; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +/// +/// Represents a handler able to process events. +/// +/// The type of the events handled by this instance. +public class OpenIddictServerHandler : IOpenIddictServerHandler where TContext : BaseContext { + private readonly Func _handler; + /// - /// Represents a handler able to process events. + /// Creates a new event using the specified handler delegate. /// - /// The type of the events handled by this instance. - public class OpenIddictServerHandler : IOpenIddictServerHandler where TContext : BaseContext - { - private readonly Func _handler; - - /// - /// Creates a new event using the specified handler delegate. - /// - /// The event handler delegate. - public OpenIddictServerHandler(Func handler) - => _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + /// The event handler delegate. + public OpenIddictServerHandler(Func handler) + => _handler = handler ?? throw new ArgumentNullException(nameof(handler)); - /// - /// Processes the event. - /// - /// The event to process. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public ValueTask HandleAsync(TContext context) - => _handler(context ?? throw new ArgumentNullException(nameof(context))); - } + /// + /// Processes the event. + /// + /// The event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public ValueTask HandleAsync(TContext context) + => _handler(context ?? throw new ArgumentNullException(nameof(context))); } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlerDescriptor.cs b/src/OpenIddict.Server/OpenIddictServerHandlerDescriptor.cs index 808a6bdf..a270f775 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlerDescriptor.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlerDescriptor.cs @@ -14,274 +14,273 @@ using Microsoft.Extensions.DependencyInjection; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +/// +/// Represents an immutable descriptor of an OpenIddict server event handler. +/// +[DebuggerDisplay("{ServiceDescriptor?.ServiceType}")] +public class OpenIddictServerHandlerDescriptor { /// - /// Represents an immutable descriptor of an OpenIddict server event handler. + /// Creates a new instance of the class. /// - [DebuggerDisplay("{ServiceDescriptor?.ServiceType}")] - public class OpenIddictServerHandlerDescriptor - { - /// - /// Creates a new instance of the class. - /// - private OpenIddictServerHandlerDescriptor() { } + private OpenIddictServerHandlerDescriptor() { } - /// - /// Gets the context type associated with the event. - /// - public Type ContextType { get; private set; } = default!; + /// + /// Gets the context type associated with the event. + /// + public Type ContextType { get; private set; } = default!; - /// - /// Gets the list of filters responsible of excluding the handler - /// from the activated handlers if it doesn't meet the criteria. - /// - public ImmutableArray FilterTypes { get; private set; } = ImmutableArray.Create(); + /// + /// Gets the list of filters responsible of excluding the handler + /// from the activated handlers if it doesn't meet the criteria. + /// + public ImmutableArray FilterTypes { get; private set; } = ImmutableArray.Create(); - /// - /// Gets the order assigned to the handler. - /// - public int Order { get; private set; } + /// + /// Gets the order assigned to the handler. + /// + public int Order { get; private set; } - /// - /// Gets the service descriptor associated with the handler. - /// - public ServiceDescriptor ServiceDescriptor { get; private set; } = default!; + /// + /// Gets the service descriptor associated with the handler. + /// + public ServiceDescriptor ServiceDescriptor { get; private set; } = default!; + + /// + /// Gets the type associated with the handler. + /// + public OpenIddictServerHandlerType Type { get; private set; } + + /// + /// Creates a builder allowing to initialize an immutable descriptor. + /// + /// The event context type. + /// A new descriptor builder. + public static Builder CreateBuilder() where TContext : BaseContext + => new Builder(); + + /// + /// Contains methods allowing to build a descriptor instance. + /// + /// The event context type. + public class Builder where TContext : BaseContext + { + private ServiceDescriptor? _descriptor; + private readonly List _filters = new(); + private int _order; + private OpenIddictServerHandlerType _type; /// - /// Gets the type associated with the handler. + /// Adds the type of a handler filter to the filters list. /// - public OpenIddictServerHandlerType Type { get; private set; } + /// The event handler filter type. + /// The builder instance, so that calls can be easily chained. + public Builder AddFilter(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!typeof(IOpenIddictServerHandlerFilter<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0104)); + } + + _filters.Add(type); + + return this; + } /// - /// Creates a builder allowing to initialize an immutable descriptor. + /// Adds the type of a handler filter to the filters list. /// - /// The event context type. - /// A new descriptor builder. - public static Builder CreateBuilder() where TContext : BaseContext - => new Builder(); + /// The event handler filter type. + /// The builder instance, so that calls can be easily chained. + public Builder AddFilter() + where TFilter : IOpenIddictServerHandlerFilter + => AddFilter(typeof(TFilter)); /// - /// Contains methods allowing to build a descriptor instance. + /// Imports the properties set on the specified descriptor. /// - /// The event context type. - public class Builder where TContext : BaseContext + /// The existing descriptor properties are copied from. + /// All the properties previously set on this instance are automatically replaced. + /// The builder instance, so that calls can be easily chained. + public Builder Import(OpenIddictServerHandlerDescriptor descriptor) { - private ServiceDescriptor? _descriptor; - private readonly List _filters = new(); - private int _order; - private OpenIddictServerHandlerType _type; - - /// - /// Adds the type of a handler filter to the filters list. - /// - /// The event handler filter type. - /// The builder instance, so that calls can be easily chained. - public Builder AddFilter(Type type) + if (descriptor is null) { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - if (!typeof(IOpenIddictServerHandlerFilter<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0104)); - } + if (descriptor.ContextType != typeof(TContext)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0284)); + } - _filters.Add(type); + _descriptor = descriptor.ServiceDescriptor; + _filters.Clear(); + _filters.AddRange(descriptor.FilterTypes); + _order = descriptor.Order; + _type = descriptor.Type; - return this; - } + return this; + } - /// - /// Adds the type of a handler filter to the filters list. - /// - /// The event handler filter type. - /// The builder instance, so that calls can be easily chained. - public Builder AddFilter() - where TFilter : IOpenIddictServerHandlerFilter - => AddFilter(typeof(TFilter)); - - /// - /// Imports the properties set on the specified descriptor. - /// - /// The existing descriptor properties are copied from. - /// All the properties previously set on this instance are automatically replaced. - /// The builder instance, so that calls can be easily chained. - public Builder Import(OpenIddictServerHandlerDescriptor descriptor) + /// + /// Sets the service descriptor. + /// + /// The service descriptor. + /// The builder instance, so that calls can be easily chained. + public Builder SetServiceDescriptor(ServiceDescriptor descriptor) + { + if (descriptor is null) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - if (descriptor.ContextType != typeof(TContext)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0284)); - } - - _descriptor = descriptor.ServiceDescriptor; - _filters.Clear(); - _filters.AddRange(descriptor.FilterTypes); - _order = descriptor.Order; - _type = descriptor.Type; - - return this; + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Sets the service descriptor. - /// - /// The service descriptor. - /// The builder instance, so that calls can be easily chained. - public Builder SetServiceDescriptor(ServiceDescriptor descriptor) + var type = descriptor.ServiceType; + if (!typeof(IOpenIddictServerHandler<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0104)); + } - var type = descriptor.ServiceType; - if (!typeof(IOpenIddictServerHandler<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0104)); - } + _descriptor = descriptor; - _descriptor = descriptor; + return this; + } - return this; - } + /// + /// Sets the order in which the event handler will be invoked. + /// + /// The handler order. + /// The builder instance, so that calls can be easily chained. + public Builder SetOrder(int order) + { + _order = order; - /// - /// Sets the order in which the event handler will be invoked. - /// - /// The handler order. - /// The builder instance, so that calls can be easily chained. - public Builder SetOrder(int order) - { - _order = order; + return this; + } - return this; + /// + /// Sets the type associated to the handler. + /// + /// The handler type. + /// The builder instance, so that calls can be easily chained. + public Builder SetType(OpenIddictServerHandlerType type) + { + if (!Enum.IsDefined(typeof(OpenIddictServerHandlerType), type)) + { + throw new InvalidEnumArgumentException(nameof(type), (int) type, typeof(OpenIddictServerHandlerType)); } - /// - /// Sets the type associated to the handler. - /// - /// The handler type. - /// The builder instance, so that calls can be easily chained. - public Builder SetType(OpenIddictServerHandlerType type) - { - if (!Enum.IsDefined(typeof(OpenIddictServerHandlerType), type)) - { - throw new InvalidEnumArgumentException(nameof(type), (int) type, typeof(OpenIddictServerHandlerType)); - } + _type = type; - _type = type; + return this; + } - return this; + /// + /// Configures the descriptor to use the specified inline handler. + /// + /// The handler instance. + /// The builder instance, so that calls can be easily chained. + public Builder UseInlineHandler(Func handler) + { + if (handler is null) + { + throw new ArgumentNullException(nameof(handler)); } - /// - /// Configures the descriptor to use the specified inline handler. - /// - /// The handler instance. - /// The builder instance, so that calls can be easily chained. - public Builder UseInlineHandler(Func handler) - { - if (handler is null) - { - throw new ArgumentNullException(nameof(handler)); - } + return UseSingletonHandler(new OpenIddictServerHandler(handler)); + } - return UseSingletonHandler(new OpenIddictServerHandler(handler)); - } + /// + /// Configures the descriptor to use the specified scoped handler. + /// + /// The handler type. + /// The builder instance, so that calls can be easily chained. + public Builder UseScopedHandler() + where THandler : IOpenIddictServerHandler + => SetServiceDescriptor(new ServiceDescriptor( + typeof(THandler), typeof(THandler), ServiceLifetime.Scoped)); - /// - /// Configures the descriptor to use the specified scoped handler. - /// - /// The handler type. - /// The builder instance, so that calls can be easily chained. - public Builder UseScopedHandler() - where THandler : IOpenIddictServerHandler - => SetServiceDescriptor(new ServiceDescriptor( - typeof(THandler), typeof(THandler), ServiceLifetime.Scoped)); - - /// - /// Configures the descriptor to use the specified scoped handler. - /// - /// The handler type. - /// The factory used to create the handler. - /// The builder instance, so that calls can be easily chained. - public Builder UseScopedHandler(Func factory) - where THandler : IOpenIddictServerHandler + /// + /// Configures the descriptor to use the specified scoped handler. + /// + /// The handler type. + /// The factory used to create the handler. + /// The builder instance, so that calls can be easily chained. + public Builder UseScopedHandler(Func factory) + where THandler : IOpenIddictServerHandler + { + if (factory is null) { - if (factory is null) - { - throw new ArgumentNullException(nameof(factory)); - } - - return SetServiceDescriptor(new ServiceDescriptor( - typeof(THandler), factory, ServiceLifetime.Scoped)); + throw new ArgumentNullException(nameof(factory)); } - /// - /// Configures the descriptor to use the specified singleton handler. - /// - /// The handler type. - /// The builder instance, so that calls can be easily chained. - public Builder UseSingletonHandler() - where THandler : IOpenIddictServerHandler - => SetServiceDescriptor(new ServiceDescriptor( - typeof(THandler), typeof(THandler), ServiceLifetime.Singleton)); - - /// - /// Configures the descriptor to use the specified singleton handler. - /// - /// The handler type. - /// The factory used to create the handler. - /// The builder instance, so that calls can be easily chained. - public Builder UseSingletonHandler(Func factory) - where THandler : IOpenIddictServerHandler - { - if (factory is null) - { - throw new ArgumentNullException(nameof(factory)); - } + return SetServiceDescriptor(new ServiceDescriptor( + typeof(THandler), factory, ServiceLifetime.Scoped)); + } - return SetServiceDescriptor(new ServiceDescriptor( - typeof(THandler), factory, ServiceLifetime.Singleton)); - } + /// + /// Configures the descriptor to use the specified singleton handler. + /// + /// The handler type. + /// The builder instance, so that calls can be easily chained. + public Builder UseSingletonHandler() + where THandler : IOpenIddictServerHandler + => SetServiceDescriptor(new ServiceDescriptor( + typeof(THandler), typeof(THandler), ServiceLifetime.Singleton)); - /// - /// Configures the descriptor to use the specified singleton handler. - /// - /// The handler type. - /// The handler instance. - /// The builder instance, so that calls can be easily chained. - public Builder UseSingletonHandler(THandler handler) - where THandler : IOpenIddictServerHandler + /// + /// Configures the descriptor to use the specified singleton handler. + /// + /// The handler type. + /// The factory used to create the handler. + /// The builder instance, so that calls can be easily chained. + public Builder UseSingletonHandler(Func factory) + where THandler : IOpenIddictServerHandler + { + if (factory is null) { - if (handler is null) - { - throw new ArgumentNullException(nameof(handler)); - } - - return SetServiceDescriptor(new ServiceDescriptor(typeof(THandler), handler)); + throw new ArgumentNullException(nameof(factory)); } - /// - /// Build a new descriptor instance, based on the parameters that were previously set. - /// - /// The builder instance, so that calls can be easily chained. - public OpenIddictServerHandlerDescriptor Build() => new OpenIddictServerHandlerDescriptor + return SetServiceDescriptor(new ServiceDescriptor( + typeof(THandler), factory, ServiceLifetime.Singleton)); + } + + /// + /// Configures the descriptor to use the specified singleton handler. + /// + /// The handler type. + /// The handler instance. + /// The builder instance, so that calls can be easily chained. + public Builder UseSingletonHandler(THandler handler) + where THandler : IOpenIddictServerHandler + { + if (handler is null) { - ContextType = typeof(TContext), - FilterTypes = _filters.ToImmutableArray(), - Order = _order, - ServiceDescriptor = _descriptor ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0105)), - Type = _type - }; + throw new ArgumentNullException(nameof(handler)); + } + + return SetServiceDescriptor(new ServiceDescriptor(typeof(THandler), handler)); } + + /// + /// Build a new descriptor instance, based on the parameters that were previously set. + /// + /// The builder instance, so that calls can be easily chained. + public OpenIddictServerHandlerDescriptor Build() => new OpenIddictServerHandlerDescriptor + { + ContextType = typeof(TContext), + FilterTypes = _filters.ToImmutableArray(), + Order = _order, + ServiceDescriptor = _descriptor ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0105)), + Type = _type + }; } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs index 7d07f08e..e84795c8 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs @@ -9,617 +9,616 @@ using System.ComponentModel; using System.Threading.Tasks; using static OpenIddict.Server.OpenIddictServerEvents; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +[EditorBrowsable(EditorBrowsableState.Advanced)] +public static class OpenIddictServerHandlerFilters { - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static class OpenIddictServerHandlerFilters + /// + /// Represents a filter that excludes the associated handlers if no access token is generated. + /// + public class RequireAccessTokenGenerated : IOpenIddictServerHandlerFilter { - /// - /// Represents a filter that excludes the associated handlers if no access token is generated. - /// - public class RequireAccessTokenGenerated : IOpenIddictServerHandlerFilter + public ValueTask IsActiveAsync(ProcessSignInContext context) { - public ValueTask IsActiveAsync(ProcessSignInContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.GenerateAccessToken); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.GenerateAccessToken); } + } - /// - /// Represents a filter that excludes the associated handlers if no access token is validated. - /// - public class RequireAccessTokenValidated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no access token is validated. + /// + public class RequireAccessTokenValidated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessAuthenticationContext context) { - public ValueTask IsActiveAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.ValidateAccessToken); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.ValidateAccessToken); } + } - /// - /// Represents a filter that excludes the associated handlers if no authorization code is generated. - /// - public class RequireAuthorizationCodeGenerated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no authorization code is generated. + /// + public class RequireAuthorizationCodeGenerated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessSignInContext context) { - public ValueTask IsActiveAsync(ProcessSignInContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.GenerateAuthorizationCode); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.GenerateAuthorizationCode); } + } - /// - /// Represents a filter that excludes the associated handlers if no authorization code is validated. - /// - public class RequireAuthorizationCodeValidated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no authorization code is validated. + /// + public class RequireAuthorizationCodeValidated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessAuthenticationContext context) { - public ValueTask IsActiveAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.ValidateAuthorizationCode); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.ValidateAuthorizationCode); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not an authorization request. - /// - public class RequireAuthorizationRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not an authorization request. + /// + public class RequireAuthorizationRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Authorization); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Authorization); } + } - /// - /// Represents a filter that excludes the associated handlers if authorization storage was not enabled. - /// - public class RequireAuthorizationStorageEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if authorization storage was not enabled. + /// + public class RequireAuthorizationStorageEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!context.Options.DisableAuthorizationStorage); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!context.Options.DisableAuthorizationStorage); } + } - /// - /// Represents a filter that excludes the associated handlers when no client identifier is received. - /// - public class RequireClientIdParameter : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers when no client identifier is received. + /// + public class RequireClientIdParameter : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!string.IsNullOrEmpty(context.Transaction.Request?.ClientId)); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!string.IsNullOrEmpty(context.Transaction.Request?.ClientId)); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not a configuration request. - /// - public class RequireConfigurationRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not a configuration request. + /// + public class RequireConfigurationRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Configuration); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Configuration); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not a cryptography request. - /// - public class RequireCryptographyRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not a cryptography request. + /// + public class RequireCryptographyRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Cryptography); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Cryptography); } + } - /// - /// Represents a filter that excludes the associated handlers if the degraded mode was not enabled. - /// - public class RequireDegradedModeDisabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the degraded mode was not enabled. + /// + public class RequireDegradedModeDisabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!context.Options.EnableDegradedMode); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!context.Options.EnableDegradedMode); } + } - /// - /// Represents a filter that excludes the associated handlers if no device code is generated. - /// - public class RequireDeviceCodeGenerated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no device code is generated. + /// + public class RequireDeviceCodeGenerated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessSignInContext context) { - public ValueTask IsActiveAsync(ProcessSignInContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.GenerateDeviceCode); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.GenerateDeviceCode); } + } - /// - /// Represents a filter that excludes the associated handlers if no device code is validated. - /// - public class RequireDeviceCodeValidated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no device code is validated. + /// + public class RequireDeviceCodeValidated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessAuthenticationContext context) { - public ValueTask IsActiveAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.ValidateDeviceCode); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.ValidateDeviceCode); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not a device request. - /// - public class RequireDeviceRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not a device request. + /// + public class RequireDeviceRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Device); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Device); } + } - /// - /// Represents a filter that excludes the associated handlers if endpoint permissions were disabled. - /// - public class RequireEndpointPermissionsEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if endpoint permissions were disabled. + /// + public class RequireEndpointPermissionsEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!context.Options.IgnoreEndpointPermissions); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!context.Options.IgnoreEndpointPermissions); } + } - /// - /// Represents a filter that excludes the associated handlers if no generic token is validated. - /// - public class RequireGenericTokenValidated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no generic token is validated. + /// + public class RequireGenericTokenValidated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessAuthenticationContext context) { - public ValueTask IsActiveAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.ValidateGenericToken); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.ValidateGenericToken); } + } - /// - /// Represents a filter that excludes the associated handlers if grant type permissions were disabled. - /// - public class RequireGrantTypePermissionsEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if grant type permissions were disabled. + /// + public class RequireGrantTypePermissionsEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!context.Options.IgnoreGrantTypePermissions); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!context.Options.IgnoreGrantTypePermissions); } + } - /// - /// Represents a filter that excludes the associated handlers if no identity token is generated. - /// - public class RequireIdentityTokenGenerated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no identity token is generated. + /// + public class RequireIdentityTokenGenerated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessSignInContext context) { - public ValueTask IsActiveAsync(ProcessSignInContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.GenerateIdentityToken); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.GenerateIdentityToken); } + } - /// - /// Represents a filter that excludes the associated handlers if no identity token is validated. - /// - public class RequireIdentityTokenValidated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no identity token is validated. + /// + public class RequireIdentityTokenValidated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessAuthenticationContext context) { - public ValueTask IsActiveAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.ValidateIdentityToken); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.ValidateIdentityToken); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not an introspection request. - /// - public class RequireIntrospectionRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not an introspection request. + /// + public class RequireIntrospectionRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Introspection); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Introspection); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not a logout request. - /// - public class RequireLogoutRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not a logout request. + /// + public class RequireLogoutRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Logout); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Logout); } + } - /// - /// Represents a filter that excludes the associated handlers when no post_logout_redirect_uri is received. - /// - public class RequirePostLogoutRedirectUriParameter : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers when no post_logout_redirect_uri is received. + /// + public class RequirePostLogoutRedirectUriParameter : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!string.IsNullOrEmpty(context.Transaction.Request?.PostLogoutRedirectUri)); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!string.IsNullOrEmpty(context.Transaction.Request?.PostLogoutRedirectUri)); } + } - /// - /// Represents a filter that excludes the associated handlers if reference access tokens are disabled. - /// - public class RequireReferenceAccessTokensEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if reference access tokens are disabled. + /// + public class RequireReferenceAccessTokensEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Options.UseReferenceAccessTokens); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Options.UseReferenceAccessTokens); } + } - /// - /// Represents a filter that excludes the associated handlers if reference refresh tokens are disabled. - /// - public class RequireReferenceRefreshTokensEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if reference refresh tokens are disabled. + /// + public class RequireReferenceRefreshTokensEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Options.UseReferenceRefreshTokens); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Options.UseReferenceRefreshTokens); } + } - /// - /// Represents a filter that excludes the associated handlers if no refresh token is generated. - /// - public class RequireRefreshTokenGenerated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no refresh token is generated. + /// + public class RequireRefreshTokenGenerated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessSignInContext context) { - public ValueTask IsActiveAsync(ProcessSignInContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.GenerateRefreshToken); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.GenerateRefreshToken); } + } - /// - /// Represents a filter that excludes the associated handlers if no refresh token is validated. - /// - public class RequireRefreshTokenValidated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no refresh token is validated. + /// + public class RequireRefreshTokenValidated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessAuthenticationContext context) { - public ValueTask IsActiveAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.ValidateRefreshToken); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.ValidateRefreshToken); } + } - /// - /// Represents a filter that excludes the associated handlers if response type permissions were disabled. - /// - public class RequireResponseTypePermissionsEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if response type permissions were disabled. + /// + public class RequireResponseTypePermissionsEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!context.Options.IgnoreResponseTypePermissions); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!context.Options.IgnoreResponseTypePermissions); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not a revocation request. - /// - public class RequireRevocationRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not a revocation request. + /// + public class RequireRevocationRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Revocation); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Revocation); } + } - /// - /// Represents a filter that excludes the associated handlers if scope permissions were disabled. - /// - public class RequireScopePermissionsEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if scope permissions were disabled. + /// + public class RequireScopePermissionsEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!context.Options.IgnoreScopePermissions); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!context.Options.IgnoreScopePermissions); } + } - /// - /// Represents a filter that excludes the associated handlers if scope validation was not enabled. - /// - public class RequireScopeValidationEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if scope validation was not enabled. + /// + public class RequireScopeValidationEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!context.Options.DisableScopeValidation); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!context.Options.DisableScopeValidation); } + } - /// - /// Represents a filter that excludes the associated handlers if sliding refresh token expiration was disabled. - /// - public class RequireSlidingRefreshTokenExpirationEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if sliding refresh token expiration was disabled. + /// + public class RequireSlidingRefreshTokenExpirationEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!context.Options.DisableSlidingRefreshTokenExpiration); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!context.Options.DisableSlidingRefreshTokenExpiration); } + } - /// - /// Represents a filter that excludes the associated handlers if no token entry is created in the database. - /// - public class RequireTokenEntryCreated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no token entry is created in the database. + /// + public class RequireTokenEntryCreated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(GenerateTokenContext context) { - public ValueTask IsActiveAsync(GenerateTokenContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.CreateTokenEntry); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.CreateTokenEntry); } + } - /// - /// Represents a filter that excludes the associated handlers if the token payload is not persisted in the database. - /// - public class RequireTokenPayloadPersisted : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the token payload is not persisted in the database. + /// + public class RequireTokenPayloadPersisted : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(GenerateTokenContext context) { - public ValueTask IsActiveAsync(GenerateTokenContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.PersistTokenPayload); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.PersistTokenPayload); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not a token request. - /// - public class RequireTokenRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not a token request. + /// + public class RequireTokenRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Token); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Token); } + } - /// - /// Represents a filter that excludes the associated handlers if token storage was not enabled. - /// - public class RequireTokenStorageEnabled : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if token storage was not enabled. + /// + public class RequireTokenStorageEnabled : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(!context.Options.DisableTokenStorage); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(!context.Options.DisableTokenStorage); } + } - /// - /// Represents a filter that excludes the associated handlers if no user code is generated. - /// - public class RequireUserCodeGenerated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no user code is generated. + /// + public class RequireUserCodeGenerated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessSignInContext context) { - public ValueTask IsActiveAsync(ProcessSignInContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.GenerateUserCode); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.GenerateUserCode); } + } - /// - /// Represents a filter that excludes the associated handlers if no user code is validated. - /// - public class RequireUserCodeValidated : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if no user code is validated. + /// + public class RequireUserCodeValidated : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(ProcessAuthenticationContext context) { - public ValueTask IsActiveAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.ValidateUserCode); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.ValidateUserCode); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not a userinfo request. - /// - public class RequireUserinfoRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not a userinfo request. + /// + public class RequireUserinfoRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Userinfo); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Userinfo); } + } - /// - /// Represents a filter that excludes the associated handlers if the request is not a verification request. - /// - public class RequireVerificationRequest : IOpenIddictServerHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if the request is not a verification request. + /// + public class RequireVerificationRequest : IOpenIddictServerHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Verification); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.EndpointType == OpenIddictServerEndpointType.Verification); } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlerType.cs b/src/OpenIddict.Server/OpenIddictServerHandlerType.cs index f1c6867a..fb9251e8 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlerType.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlerType.cs @@ -4,26 +4,25 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Server +namespace OpenIddict.Server; + +/// +/// Represents the type of an OpenIddict server handler. +/// +public enum OpenIddictServerHandlerType { /// - /// Represents the type of an OpenIddict server handler. + /// The handler is of an unspecified type. /// - public enum OpenIddictServerHandlerType - { - /// - /// The handler is of an unspecified type. - /// - Unknown = 0, + Unknown = 0, - /// - /// The handler is a built-in handler, provided as part of the official OpenIddict packages. - /// - BuiltIn = 1, + /// + /// The handler is a built-in handler, provided as part of the official OpenIddict packages. + /// + BuiltIn = 1, - /// - /// The handler is a custom handler, registered by the end user or a third-party package. - /// - Custom = 2 - } + /// + /// The handler is a custom handler, registered by the end user or a third-party package. + /// + Custom = 2 } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs index 4bde2eb5..4b4eb7a4 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs @@ -19,439 +19,485 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerHandlers { - public static partial class OpenIddictServerHandlers + public static class Authentication { - public static class Authentication + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Authorization request top-level processing: + */ + ExtractAuthorizationRequest.Descriptor, + ValidateAuthorizationRequest.Descriptor, + HandleAuthorizationRequest.Descriptor, + ApplyAuthorizationResponse.Descriptor, + ApplyAuthorizationResponse.Descriptor, + ApplyAuthorizationResponse.Descriptor, + ApplyAuthorizationResponse.Descriptor, + + /* + * Authorization request validation: + */ + ValidateRequestParameter.Descriptor, + ValidateRequestUriParameter.Descriptor, + ValidateClientIdParameter.Descriptor, + ValidateRedirectUriParameter.Descriptor, + ValidateResponseTypeParameter.Descriptor, + ValidateResponseModeParameter.Descriptor, + ValidateScopeParameter.Descriptor, + ValidateNonceParameter.Descriptor, + ValidatePromptParameter.Descriptor, + ValidateProofKeyForCodeExchangeParameters.Descriptor, + ValidateClientId.Descriptor, + ValidateClientType.Descriptor, + ValidateClientRedirectUri.Descriptor, + ValidateScopes.Descriptor, + ValidateEndpointPermissions.Descriptor, + ValidateGrantTypePermissions.Descriptor, + ValidateResponseTypePermissions.Descriptor, + ValidateScopePermissions.Descriptor, + ValidateProofKeyForCodeExchangeRequirement.Descriptor, + + /* + * Authorization response processing: + */ + AttachRedirectUri.Descriptor, + InferResponseMode.Descriptor, + AttachResponseState.Descriptor); + + /// + /// Contains the logic responsible of extracting authorization requests and invoking the corresponding event handlers. + /// + public class ExtractAuthorizationRequest : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Authorization request top-level processing: - */ - ExtractAuthorizationRequest.Descriptor, - ValidateAuthorizationRequest.Descriptor, - HandleAuthorizationRequest.Descriptor, - ApplyAuthorizationResponse.Descriptor, - ApplyAuthorizationResponse.Descriptor, - ApplyAuthorizationResponse.Descriptor, - ApplyAuthorizationResponse.Descriptor, - - /* - * Authorization request validation: - */ - ValidateRequestParameter.Descriptor, - ValidateRequestUriParameter.Descriptor, - ValidateClientIdParameter.Descriptor, - ValidateRedirectUriParameter.Descriptor, - ValidateResponseTypeParameter.Descriptor, - ValidateResponseModeParameter.Descriptor, - ValidateScopeParameter.Descriptor, - ValidateNonceParameter.Descriptor, - ValidatePromptParameter.Descriptor, - ValidateProofKeyForCodeExchangeParameters.Descriptor, - ValidateClientId.Descriptor, - ValidateClientType.Descriptor, - ValidateClientRedirectUri.Descriptor, - ValidateScopes.Descriptor, - ValidateEndpointPermissions.Descriptor, - ValidateGrantTypePermissions.Descriptor, - ValidateResponseTypePermissions.Descriptor, - ValidateScopePermissions.Descriptor, - ValidateProofKeyForCodeExchangeRequirement.Descriptor, - - /* - * Authorization response processing: - */ - AttachRedirectUri.Descriptor, - InferResponseMode.Descriptor, - AttachResponseState.Descriptor); + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractAuthorizationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting authorization requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractAuthorizationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractAuthorizationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ExtractAuthorizationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ExtractAuthorizationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0027)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6030), notification.Request); + if (notification.Request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0027)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6030), notification.Request); } + } + + /// + /// Contains the logic responsible of validating authorization requests and invoking the corresponding event handlers. + /// + public class ValidateAuthorizationRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateAuthorizationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating authorization requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAuthorizationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractAuthorizationRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateAuthorizationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractAuthorizationRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var notification = new ValidateAuthorizationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new ValidateAuthorizationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the redirect_uri without triggering a new validation process. - context.Transaction.SetProperty(typeof(ValidateAuthorizationRequestContext).FullName!, notification); + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the redirect_uri without triggering a new validation process. + context.Transaction.SetProperty(typeof(ValidateAuthorizationRequestContext).FullName!, notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (string.IsNullOrEmpty(notification.RedirectUri)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0028)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6031)); + if (string.IsNullOrEmpty(notification.RedirectUri)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0028)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6031)); } + } + + /// + /// Contains the logic responsible of handling authorization requests and invoking the corresponding event handlers. + /// + public class HandleAuthorizationRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleAuthorizationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling authorization requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleAuthorizationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateAuthorizationRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleAuthorizationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateAuthorizationRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var notification = new HandleAuthorizationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } + + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } + + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } + + if (notification.Principal is not null) + { + var @event = new ProcessSignInContext(context.Transaction) { - throw new ArgumentNullException(nameof(context)); + Principal = notification.Principal, + Response = new OpenIddictResponse() + }; + + if (notification.Parameters.Count > 0) + { + foreach (var parameter in notification.Parameters) + { + @event.Parameters.Add(parameter.Key, parameter.Value); + } } - var notification = new HandleAuthorizationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + await _dispatcher.DispatchAsync(@event); - if (notification.IsRequestHandled) + if (@event.IsRequestHandled) { context.HandleRequest(); return; } - else if (notification.IsRequestSkipped) + else if (@event.IsRequestSkipped) { context.SkipRequest(); return; } - else if (notification.IsRejected) + else if (@event.IsRejected) { context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); + error: @event.Error ?? Errors.InvalidRequest, + description: @event.ErrorDescription, + uri: @event.ErrorUri); return; } + } - if (notification.Principal is not null) - { - var @event = new ProcessSignInContext(context.Transaction) - { - Principal = notification.Principal, - Response = new OpenIddictResponse() - }; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0029)); + } + } - if (notification.Parameters.Count > 0) - { - foreach (var parameter in notification.Parameters) - { - @event.Parameters.Add(parameter.Key, parameter.Value); - } - } + /// + /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// + public class ApplyAuthorizationResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; - await _dispatcher.DispatchAsync(@event); + public ApplyAuthorizationResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; - if (@event.IsRequestHandled) - { - context.HandleRequest(); - return; - } + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - else if (@event.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + var notification = new ApplyAuthorizationResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (@event.IsRejected) - { - context.Reject( - error: @event.Error ?? Errors.InvalidRequest, - description: @event.ErrorDescription, - uri: @event.ErrorUri); - return; - } - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0029)); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0030)); } + } + /// + /// Contains the logic responsible of rejecting authorization requests that specify the unsupported request parameter. + /// + public class ValidateRequestParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyAuthorizationResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyAuthorizationResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ApplyAuthorizationResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + // Reject requests using the unsupported request parameter. + if (!string.IsNullOrEmpty(context.Request.Request)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6032), Parameters.Request); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + context.Reject( + error: Errors.RequestNotSupported, + description: SR.FormatID2028(Parameters.Request), + uri: SR.FormatID8000(SR.ID2028)); - throw new InvalidOperationException(SR.GetResourceString(SR.ID0030)); + return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authorization requests that specify the unsupported request_uri parameter. + /// + public class ValidateRequestUriParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authorization requests that specify the unsupported request parameter. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateRequestParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateRequestParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject requests using the unsupported request parameter. - if (!string.IsNullOrEmpty(context.Request.Request)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6032), Parameters.Request); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.RequestNotSupported, - description: SR.FormatID2028(Parameters.Request), - uri: SR.FormatID8000(SR.ID2028)); + // Reject requests using the unsupported request_uri parameter. + if (!string.IsNullOrEmpty(context.Request.RequestUri)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6032), Parameters.RequestUri); - return default; - } + context.Reject( + error: Errors.RequestUriNotSupported, + description: SR.FormatID2028(Parameters.RequestUri), + uri: SR.FormatID8000(SR.ID2028)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authorization requests that lack the mandatory client_id parameter. + /// + public class ValidateClientIdParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authorization requests that specify the unsupported request_uri parameter. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateRequestUriParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateRequestUriParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateRequestParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject requests using the unsupported request_uri parameter. - if (!string.IsNullOrEmpty(context.Request.RequestUri)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6032), Parameters.RequestUri); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.RequestUriNotSupported, - description: SR.FormatID2028(Parameters.RequestUri), - uri: SR.FormatID8000(SR.ID2028)); + // client_id is a required parameter and MUST cause an error when missing. + // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. + if (string.IsNullOrEmpty(context.ClientId)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.ClientId); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authorization requests that lack the mandatory redirect_uri parameter. + /// + public class ValidateRedirectUriParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authorization requests that lack the mandatory client_id parameter. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientIdParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateRequestUriParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // client_id is a required parameter and MUST cause an error when missing. - // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. - if (string.IsNullOrEmpty(context.ClientId)) + // While redirect_uri was not mandatory in OAuth 2.0, this parameter + // is now declared as REQUIRED and MUST cause an error when missing. + // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. + // To keep OpenIddict compatible with pure OAuth 2.0 clients, an error + // is only returned if the request was made by an OpenID Connect client. + if (string.IsNullOrEmpty(context.RedirectUri)) + { + if (context.Request.HasScope(Scopes.OpenId)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.ClientId); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.RedirectUri); context.Reject( error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.ClientId), + description: SR.FormatID2029(Parameters.RedirectUri), uri: SR.FormatID8000(SR.ID2029)); return default; @@ -459,1287 +505,1240 @@ namespace OpenIddict.Server return default; } - } - /// - /// Contains the logic responsible of rejecting authorization requests that lack the mandatory redirect_uri parameter. - /// - public class ValidateRedirectUriParameter : IOpenIddictServerHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // While redirect_uri was not mandatory in OAuth 2.0, this parameter - // is now declared as REQUIRED and MUST cause an error when missing. - // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. - // To keep OpenIddict compatible with pure OAuth 2.0 clients, an error - // is only returned if the request was made by an OpenID Connect client. - if (string.IsNullOrEmpty(context.RedirectUri)) - { - if (context.Request.HasScope(Scopes.OpenId)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.RedirectUri); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.RedirectUri), - uri: SR.FormatID8000(SR.ID2029)); - - return default; - } - - return default; - } - - // Note: when specified, redirect_uri MUST be an absolute URI. - // See http://tools.ietf.org/html/rfc6749#section-3.1.2 - // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. - // - // Note: on Linux/macOS, "/path" URLs are treated as valid absolute file URLs. - // To ensure relative redirect_uris are correctly rejected on these platforms, - // an additional check using IsWellFormedOriginalString() is made here. - // See https://github.com/dotnet/corefx/issues/22098 for more information. - if (!Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6034), Parameters.RedirectUri, context.RedirectUri); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2030(Parameters.RedirectUri), - uri: SR.FormatID8000(SR.ID2030)); + // Note: when specified, redirect_uri MUST be an absolute URI. + // See http://tools.ietf.org/html/rfc6749#section-3.1.2 + // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. + // + // Note: on Linux/macOS, "/path" URLs are treated as valid absolute file URLs. + // To ensure relative redirect_uris are correctly rejected on these platforms, + // an additional check using IsWellFormedOriginalString() is made here. + // See https://github.com/dotnet/corefx/issues/22098 for more information. + if (!Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6034), Parameters.RedirectUri, context.RedirectUri); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2030(Parameters.RedirectUri), + uri: SR.FormatID8000(SR.ID2030)); - // Note: when specified, redirect_uri MUST NOT include a fragment component. - // See http://tools.ietf.org/html/rfc6749#section-3.1.2 - // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest - if (!string.IsNullOrEmpty(uri.Fragment)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6035), Parameters.RedirectUri, context.RedirectUri); + return default; + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2031(Parameters.RedirectUri), - uri: SR.FormatID8000(SR.ID2031)); + // Note: when specified, redirect_uri MUST NOT include a fragment component. + // See http://tools.ietf.org/html/rfc6749#section-3.1.2 + // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + if (!string.IsNullOrEmpty(uri.Fragment)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6035), Parameters.RedirectUri, context.RedirectUri); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2031(Parameters.RedirectUri), + uri: SR.FormatID8000(SR.ID2031)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authorization requests that specify an invalid response_type parameter. + /// + public class ValidateResponseTypeParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authorization requests that specify an invalid response_type parameter. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateResponseTypeParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateRedirectUriParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateRedirectUriParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Reject requests missing the mandatory response_type parameter. - if (string.IsNullOrEmpty(context.Request.ResponseType)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.ResponseType); + // Reject requests missing the mandatory response_type parameter. + if (string.IsNullOrEmpty(context.Request.ResponseType)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.ResponseType); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.ResponseType), - uri: SR.FormatID8000(SR.ID2029)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.ResponseType), + uri: SR.FormatID8000(SR.ID2029)); - return default; - } + return default; + } - // Reject code flow requests if the server is not configured to allow the authorization code grant type. - if (context.Request.IsAuthorizationCodeFlow() && !context.Options.GrantTypes.Contains(GrantTypes.AuthorizationCode)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6036), context.Request.ResponseType); + // Reject code flow requests if the server is not configured to allow the authorization code grant type. + if (context.Request.IsAuthorizationCodeFlow() && !context.Options.GrantTypes.Contains(GrantTypes.AuthorizationCode)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6036), context.Request.ResponseType); - context.Reject( - error: Errors.UnsupportedResponseType, - description: SR.FormatID2032(Parameters.ResponseType), - uri: SR.FormatID8000(SR.ID2032)); + context.Reject( + error: Errors.UnsupportedResponseType, + description: SR.FormatID2032(Parameters.ResponseType), + uri: SR.FormatID8000(SR.ID2032)); - return default; - } + return default; + } - // Reject implicit flow requests if the server is not configured to allow the implicit grant type. - if (context.Request.IsImplicitFlow() && !context.Options.GrantTypes.Contains(GrantTypes.Implicit)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6036), context.Request.ResponseType); + // Reject implicit flow requests if the server is not configured to allow the implicit grant type. + if (context.Request.IsImplicitFlow() && !context.Options.GrantTypes.Contains(GrantTypes.Implicit)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6036), context.Request.ResponseType); - context.Reject( - error: Errors.UnsupportedResponseType, - description: SR.FormatID2032(Parameters.ResponseType), - uri: SR.FormatID8000(SR.ID2032)); + context.Reject( + error: Errors.UnsupportedResponseType, + description: SR.FormatID2032(Parameters.ResponseType), + uri: SR.FormatID8000(SR.ID2032)); - return default; - } + return default; + } - // Reject hybrid flow requests if the server is not configured to allow the authorization code or implicit grant types. - if (context.Request.IsHybridFlow() && (!context.Options.GrantTypes.Contains(GrantTypes.AuthorizationCode) || - !context.Options.GrantTypes.Contains(GrantTypes.Implicit))) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6036), context.Request.ResponseType); + // Reject hybrid flow requests if the server is not configured to allow the authorization code or implicit grant types. + if (context.Request.IsHybridFlow() && (!context.Options.GrantTypes.Contains(GrantTypes.AuthorizationCode) || + !context.Options.GrantTypes.Contains(GrantTypes.Implicit))) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6036), context.Request.ResponseType); - context.Reject( - error: Errors.UnsupportedResponseType, - description: SR.FormatID2032(Parameters.ResponseType), - uri: SR.FormatID8000(SR.ID2032)); + context.Reject( + error: Errors.UnsupportedResponseType, + description: SR.FormatID2032(Parameters.ResponseType), + uri: SR.FormatID8000(SR.ID2032)); - return default; - } + return default; + } - // Reject requests that specify an unsupported response_type. - var types = new HashSet(context.Request.GetResponseTypes(), StringComparer.Ordinal); - if (!context.Options.ResponseTypes.Any(type => - types.SetEquals(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6036), context.Request.ResponseType); + // Reject requests that specify an unsupported response_type. + var types = new HashSet(context.Request.GetResponseTypes(), StringComparer.Ordinal); + if (!context.Options.ResponseTypes.Any(type => + types.SetEquals(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6036), context.Request.ResponseType); - context.Reject( - error: Errors.UnsupportedResponseType, - description: SR.FormatID2032(Parameters.ResponseType), - uri: SR.FormatID8000(SR.ID2032)); - - return default; - } + context.Reject( + error: Errors.UnsupportedResponseType, + description: SR.FormatID2032(Parameters.ResponseType), + uri: SR.FormatID8000(SR.ID2032)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authorization requests that specify an invalid response_mode parameter. + /// + public class ValidateResponseModeParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authorization requests that specify an invalid response_mode parameter. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateResponseModeParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateResponseTypeParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateResponseTypeParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // response_mode=query (explicit or not) and a response_type containing id_token - // or token are not considered as a safe combination and MUST be rejected. - // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security. - if (context.Request.IsQueryResponseMode() && (context.Request.HasResponseType(ResponseTypes.IdToken) || - context.Request.HasResponseType(ResponseTypes.Token))) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6037), context.Request.ResponseType, context.Request.ResponseMode); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2033(Parameters.ResponseType, Parameters.ResponseMode), - uri: SR.FormatID8000(SR.ID2033)); - - return default; - } - - // Reject requests that specify an unsupported response_mode or don't specify a different response_mode - // if the default response_mode inferred from the response_type was explicitly disabled in the options. - if (!ValidateResponseMode(context.Request, context.Options)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6038), context.Request.ResponseMode); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2032(Parameters.ResponseMode), - uri: SR.FormatID8000(SR.ID2032)); + // response_mode=query (explicit or not) and a response_type containing id_token + // or token are not considered as a safe combination and MUST be rejected. + // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security. + if (context.Request.IsQueryResponseMode() && (context.Request.HasResponseType(ResponseTypes.IdToken) || + context.Request.HasResponseType(ResponseTypes.Token))) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6037), context.Request.ResponseType, context.Request.ResponseMode); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2033(Parameters.ResponseType, Parameters.ResponseMode), + uri: SR.FormatID8000(SR.ID2033)); return default; + } - static bool ValidateResponseMode(OpenIddictRequest request, OpenIddictServerOptions options) - { - // Note: both the fragment and query response modes are used as default response modes - // when using the implicit/hybrid and code flows if no explicit value was set. - // To ensure requests are rejected if the default response mode was manually disabled, - // the fragment and query response modes are checked first using the appropriate extensions. + // Reject requests that specify an unsupported response_mode or don't specify a different response_mode + // if the default response_mode inferred from the response_type was explicitly disabled in the options. + if (!ValidateResponseMode(context.Request, context.Options)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6038), context.Request.ResponseMode); - if (request.IsFragmentResponseMode()) - { - return options.ResponseModes.Contains(ResponseModes.Fragment); - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2032(Parameters.ResponseMode), + uri: SR.FormatID8000(SR.ID2032)); - if (request.IsQueryResponseMode()) - { - return options.ResponseModes.Contains(ResponseModes.Query); - } + return default; + } - if (string.IsNullOrEmpty(request.ResponseMode)) - { - return true; - } + return default; - return options.ResponseModes.Contains(request.ResponseMode); - } - } - } + static bool ValidateResponseMode(OpenIddictRequest request, OpenIddictServerOptions options) + { + // Note: both the fragment and query response modes are used as default response modes + // when using the implicit/hybrid and code flows if no explicit value was set. + // To ensure requests are rejected if the default response mode was manually disabled, + // the fragment and query response modes are checked first using the appropriate extensions. - /// - /// Contains the logic responsible of rejecting authorization requests that don't specify a valid scope parameter. - /// - public class ValidateScopeParameter : IOpenIddictServerHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateResponseModeParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) + if (request.IsFragmentResponseMode()) { - throw new ArgumentNullException(nameof(context)); + return options.ResponseModes.Contains(ResponseModes.Fragment); } - // Reject authorization requests containing the id_token response_type if no openid scope has been received. - if (context.Request.HasResponseType(ResponseTypes.IdToken) && !context.Request.HasScope(Scopes.OpenId)) + if (request.IsQueryResponseMode()) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6039), Scopes.OpenId); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2034(Scopes.OpenId), - uri: SR.FormatID8000(SR.ID2034)); - - return default; + return options.ResponseModes.Contains(ResponseModes.Query); } - // Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled. - if (context.Request.HasScope(Scopes.OfflineAccess) && !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken)) + if (string.IsNullOrEmpty(request.ResponseMode)) { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2035(Scopes.OfflineAccess), - uri: SR.FormatID8000(SR.ID2035)); - - return default; + return true; } - return default; + return options.ResponseModes.Contains(request.ResponseMode); } } + } + /// + /// Contains the logic responsible of rejecting authorization requests that don't specify a valid scope parameter. + /// + public class ValidateScopeParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authorization requests that don't specify a nonce. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateNonceParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateResponseModeParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateScopeParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject OpenID Connect implicit/hybrid requests missing the mandatory nonce parameter. - // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest, - // http://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters - // and http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken. - - if (!string.IsNullOrEmpty(context.Request.Nonce) || !context.Request.HasScope(Scopes.OpenId)) - { - return default; - } - - if (context.Request.IsImplicitFlow() || context.Request.IsHybridFlow()) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.Nonce); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.Nonce), - uri: SR.FormatID8000(SR.ID2029)); + // Reject authorization requests containing the id_token response_type if no openid scope has been received. + if (context.Request.HasResponseType(ResponseTypes.IdToken) && !context.Request.HasScope(Scopes.OpenId)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6039), Scopes.OpenId); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2034(Scopes.OpenId), + uri: SR.FormatID8000(SR.ID2034)); return default; } - } - - /// - /// Contains the logic responsible of rejecting authorization requests that don't specify a valid prompt parameter. - /// - public class ValidatePromptParameter : IOpenIddictServerHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateNonceParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject requests specifying prompt=none with consent/login or select_account. - if (context.Request.HasPrompt(Prompts.None) && (context.Request.HasPrompt(Prompts.Consent) || - context.Request.HasPrompt(Prompts.Login) || - context.Request.HasPrompt(Prompts.SelectAccount))) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6040)); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.Prompt), - uri: SR.FormatID8000(SR.ID2052)); - return default; - } + // Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled. + if (context.Request.HasScope(Scopes.OfflineAccess) && !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken)) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2035(Scopes.OfflineAccess), + uri: SR.FormatID8000(SR.ID2035)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authorization requests that don't specify a nonce. + /// + public class ValidateNonceParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authorization requests that don't specify valid PKCE parameters. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateProofKeyForCodeExchangeParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateScopeParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidatePromptParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // If OpenIddict was configured to require PKCE, reject the request if the code challenge - // is missing and if an authorization code was requested by the client application. - if (context.Options.RequireProofKeyForCodeExchange && - context.Request.HasResponseType(ResponseTypes.Code) && - string.IsNullOrEmpty(context.Request.CodeChallenge)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeChallenge); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.CodeChallenge), - uri: SR.FormatID8000(SR.ID2029)); - - return default; - } - - // At this point, stop validating the PKCE parameters if both the - // code_challenge and code_challenge_method parameter are missing. - if (string.IsNullOrEmpty(context.Request.CodeChallenge) && - string.IsNullOrEmpty(context.Request.CodeChallengeMethod)) - { - return default; - } - - // Ensure a code_challenge was specified if a code_challenge_method was used. - if (string.IsNullOrEmpty(context.Request.CodeChallenge)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeChallenge); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2037(Parameters.CodeChallengeMethod, Parameters.CodeChallenge), - uri: SR.FormatID8000(SR.ID2037)); - - return default; - } - - // If the plain code challenge method was not explicitly enabled, - // reject the request indicating that a method must be set. - if (string.IsNullOrEmpty(context.Request.CodeChallengeMethod) && - !context.Options.CodeChallengeMethods.Contains(CodeChallengeMethods.Plain)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeChallengeMethod); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.CodeChallengeMethod), - uri: SR.FormatID8000(SR.ID2029)); - - return default; - } - - // If a code_challenge_method was specified, ensure the algorithm is supported. - if (!string.IsNullOrEmpty(context.Request.CodeChallengeMethod) && - !context.Options.CodeChallengeMethods.Contains(context.Request.CodeChallengeMethod)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6041)); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2032(Parameters.CodeChallengeMethod), - uri: SR.FormatID8000(SR.ID2032)); - - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // When code_challenge or code_challenge_method is specified, ensure the response_type includes "code". - if (!context.Request.HasResponseType(ResponseTypes.Code)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6042)); + // Reject OpenID Connect implicit/hybrid requests missing the mandatory nonce parameter. + // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest, + // http://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters + // and http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken. - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2040(Parameters.CodeChallenge, Parameters.CodeChallengeMethod, ResponseTypes.Code), - uri: SR.FormatID8000(SR.ID2040)); - - return default; - } + if (!string.IsNullOrEmpty(context.Request.Nonce) || !context.Request.HasScope(Scopes.OpenId)) + { + return default; + } - // Reject authorization requests that contain response_type=token when a code_challenge is specified. - if (context.Request.HasResponseType(ResponseTypes.Token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6043)); + if (context.Request.IsImplicitFlow() || context.Request.IsHybridFlow()) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.Nonce); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2041(Parameters.ResponseType), - uri: SR.FormatID8000(SR.ID2041)); - - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.Nonce), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authorization requests that don't specify a valid prompt parameter. + /// + public class ValidatePromptParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authorization requests that use an invalid client_id. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientId : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateNonceParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateClientId(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateProofKeyForCodeExchangeParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + // Reject requests specifying prompt=none with consent/login or select_account. + if (context.Request.HasPrompt(Prompts.None) && (context.Request.HasPrompt(Prompts.Consent) || + context.Request.HasPrompt(Prompts.Login) || + context.Request.HasPrompt(Prompts.SelectAccount))) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6040)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6044), context.ClientId); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.Prompt), + uri: SR.FormatID8000(SR.ID2052)); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.ClientId), - uri: SR.FormatID8000(SR.ID2052)); - - return; - } + return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authorization requests that don't specify valid PKCE parameters. + /// + public class ValidateProofKeyForCodeExchangeParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authorization requests that use a - /// response_type containing token if the application is a confidential client. - /// Note: this handler is not used when the degraded mode is enabled - /// or when response type permissions enforcement is not disabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientType : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidatePromptParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + // If OpenIddict was configured to require PKCE, reject the request if the code challenge + // is missing and if an authorization code was requested by the client application. + if (context.Options.RequireProofKeyForCodeExchange && + context.Request.HasResponseType(ResponseTypes.Code) && + string.IsNullOrEmpty(context.Request.CodeChallenge)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeChallenge); - public ValidateClientType(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.CodeChallenge), + uri: SR.FormatID8000(SR.ID2029)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientId.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + return default; + } - /// - public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + // At this point, stop validating the PKCE parameters if both the + // code_challenge and code_challenge_method parameter are missing. + if (string.IsNullOrEmpty(context.Request.CodeChallenge) && + string.IsNullOrEmpty(context.Request.CodeChallengeMethod)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return default; + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + // Ensure a code_challenge was specified if a code_challenge_method was used. + if (string.IsNullOrEmpty(context.Request.CodeChallenge)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeChallenge); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2037(Parameters.CodeChallengeMethod, Parameters.CodeChallenge), + uri: SR.FormatID8000(SR.ID2037)); - // To prevent downgrade attacks, ensure that authorization requests returning an access token directly - // from the authorization endpoint are rejected if the client_id corresponds to a confidential application - // and if response type permissions enforcement was explicitly disabled in the server options. - // Users who want to enable this advanced scenario are encouraged to re-enable permissions validation. - // - // Alternatively, this handler can be removed from the handlers list using the events model APIs. - if (!context.Options.IgnoreResponseTypePermissions || !context.Request.HasResponseType(ResponseTypes.Token)) - { - return; - } + return default; + } - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Confidential)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6045), context.ClientId); + // If the plain code challenge method was not explicitly enabled, + // reject the request indicating that a method must be set. + if (string.IsNullOrEmpty(context.Request.CodeChallengeMethod) && + !context.Options.CodeChallengeMethods.Contains(CodeChallengeMethods.Plain)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeChallengeMethod); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.FormatID2043(Parameters.ResponseType), - uri: SR.FormatID8000(SR.ID2043)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.CodeChallengeMethod), + uri: SR.FormatID8000(SR.ID2029)); - return; - } + return default; } - } - /// - /// Contains the logic responsible of rejecting authorization requests that use an invalid redirect_uri. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateClientRedirectUri : IOpenIddictServerHandler - { - private readonly IOpenIddictApplicationManager _applicationManager; + // If a code_challenge_method was specified, ensure the algorithm is supported. + if (!string.IsNullOrEmpty(context.Request.CodeChallengeMethod) && + !context.Options.CodeChallengeMethods.Contains(context.Request.CodeChallengeMethod)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6041)); + + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2032(Parameters.CodeChallengeMethod), + uri: SR.FormatID8000(SR.ID2032)); + + return default; + } - public ValidateClientRedirectUri() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + // When code_challenge or code_challenge_method is specified, ensure the response_type includes "code". + if (!context.Request.HasResponseType(ResponseTypes.Code)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6042)); - public ValidateClientRedirectUri(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2040(Parameters.CodeChallenge, Parameters.CodeChallengeMethod, ResponseTypes.Code), + uri: SR.FormatID8000(SR.ID2040)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + return default; + } - /// - public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + // Reject authorization requests that contain response_type=token when a code_challenge is specified. + if (context.Request.HasResponseType(ResponseTypes.Token)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6043)); - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2041(Parameters.ResponseType), + uri: SR.FormatID8000(SR.ID2041)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + return default; + } - // If no explicit redirect_uri was specified, retrieve the addresses associated with - // the client and ensure exactly one redirect_uri was attached to the client definition. - if (string.IsNullOrEmpty(context.RedirectUri)) - { - var addresses = await _applicationManager.GetRedirectUrisAsync(application); - if (addresses.Length != 1) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.RedirectUri); + return default; + } + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.RedirectUri), - uri: SR.FormatID8000(SR.ID2029)); + /// + /// Contains the logic responsible of rejecting authorization requests that use an invalid client_id. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientId : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - return; - } + public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - context.SetRedirectUri(addresses[0]); + public ValidateClientId(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; - return; - } + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateProofKeyForCodeExchangeParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Otherwise, ensure that the specified redirect_uri is valid and is associated with the client application. - if (!await _applicationManager.ValidateRedirectUriAsync(application, context.RedirectUri)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6046), context.RedirectUri); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2043(Parameters.RedirectUri), - uri: SR.FormatID8000(SR.ID2043)); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6044), context.ClientId); - return; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2052)); + + return; } } + } + + /// + /// Contains the logic responsible of rejecting authorization requests that use a + /// response_type containing token if the application is a confidential client. + /// Note: this handler is not used when the degraded mode is enabled + /// or when response type permissions enforcement is not disabled. + /// + public class ValidateClientType : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateClientType(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting authorization requests that use unregistered scopes. - /// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateScopes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientId.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - private readonly IOpenIddictScopeManager? _scopeManager; - - public ValidateScopes(IOpenIddictScopeManager? scopeManager = null) - => _scopeManager = scopeManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler(static provider => - { - // Note: the scope manager is only resolved if the degraded mode was not enabled to ensure - // invalid core configuration exceptions are not thrown even if the managers were registered. - var options = provider.GetRequiredService>().CurrentValue; - - return options.EnableDegradedMode ? - new ValidateScopes() : - new ValidateScopes(provider.GetService() ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0016))); - }) - .SetOrder(ValidateClientRedirectUri.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If all the specified scopes are registered in the options, avoid making a database lookup. - var scopes = new HashSet(context.Request.GetScopes(), StringComparer.Ordinal); - scopes.ExceptWith(context.Options.Scopes); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - // Note: the remaining scopes are only checked if the degraded mode was not enabled, - // as this requires using the scope manager, which is never used with the degraded mode, - // even if the service was registered and resolved from the dependency injection container. - if (scopes.Count != 0 && !context.Options.EnableDegradedMode) - { - if (_scopeManager is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray())) - { - var name = await _scopeManager.GetNameAsync(scope); - if (!string.IsNullOrEmpty(name)) - { - scopes.Remove(name); - } - } - } + // To prevent downgrade attacks, ensure that authorization requests returning an access token directly + // from the authorization endpoint are rejected if the client_id corresponds to a confidential application + // and if response type permissions enforcement was explicitly disabled in the server options. + // Users who want to enable this advanced scenario are encouraged to re-enable permissions validation. + // + // Alternatively, this handler can be removed from the handlers list using the events model APIs. + if (!context.Options.IgnoreResponseTypePermissions || !context.Request.HasResponseType(ResponseTypes.Token)) + { + return; + } - // If at least one scope was not recognized, return an error. - if (scopes.Count != 0) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6047), scopes); + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Confidential)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6045), context.ClientId); - context.Reject( - error: Errors.InvalidScope, - description: SR.FormatID2052(Parameters.Scope), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.FormatID2043(Parameters.ResponseType), + uri: SR.FormatID8000(SR.ID2043)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting authorization requests that use an invalid redirect_uri. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientRedirectUri : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateClientRedirectUri() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateClientRedirectUri(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications. - /// Note: this handler is not used when the degraded mode is enabled or when endpoint permissions are disabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateEndpointPermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateScopes.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - /// - public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + // If no explicit redirect_uri was specified, retrieve the addresses associated with + // the client and ensure exactly one redirect_uri was attached to the client definition. + if (string.IsNullOrEmpty(context.RedirectUri)) { - if (context is null) + var addresses = await _applicationManager.GetRedirectUrisAsync(application); + if (addresses.Length != 1) { - throw new ArgumentNullException(nameof(context)); - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.RedirectUri); - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.RedirectUri), + uri: SR.FormatID8000(SR.ID2029)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + return; } - // Reject the request if the application is not allowed to use the authorization endpoint. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Authorization)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6048), context.ClientId); + context.SetRedirectUri(addresses[0]); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2046), - uri: SR.FormatID8000(SR.ID2046)); + return; + } - return; - } + // Otherwise, ensure that the specified redirect_uri is valid and is associated with the client application. + if (!await _applicationManager.ValidateRedirectUriAsync(application, context.RedirectUri)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6046), context.RedirectUri); + + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2043(Parameters.RedirectUri), + uri: SR.FormatID8000(SR.ID2043)); + + return; } } + } + + /// + /// Contains the logic responsible of rejecting authorization requests that use unregistered scopes. + /// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled. + /// + public class ValidateScopes : IOpenIddictServerHandler + { + private readonly IOpenIddictScopeManager? _scopeManager; + + public ValidateScopes(IOpenIddictScopeManager? scopeManager = null) + => _scopeManager = scopeManager; /// - /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications. - /// Note: this handler is not used when the degraded mode is enabled or when grant type permissions are disabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateGrantTypePermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler(static provider => + { + // Note: the scope manager is only resolved if the degraded mode was not enabled to ensure + // invalid core configuration exceptions are not thrown even if the managers were registered. + var options = provider.GetRequiredService>().CurrentValue; + + return options.EnableDegradedMode ? + new ValidateScopes() : + new ValidateScopes(provider.GetService() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0016))); + }) + .SetOrder(ValidateClientRedirectUri.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateGrantTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateGrantTypePermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + // If all the specified scopes are registered in the options, avoid making a database lookup. + var scopes = new HashSet(context.Request.GetScopes(), StringComparer.Ordinal); + scopes.ExceptWith(context.Options.Scopes); - /// - public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + // Note: the remaining scopes are only checked if the degraded mode was not enabled, + // as this requires using the scope manager, which is never used with the degraded mode, + // even if the service was registered and resolved from the dependency injection container. + if (scopes.Count != 0 && !context.Options.EnableDegradedMode) { - if (context is null) + if (_scopeManager is null) { - throw new ArgumentNullException(nameof(context)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) + await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray())) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + var name = await _scopeManager.GetNameAsync(scope); + if (!string.IsNullOrEmpty(name)) + { + scopes.Remove(name); + } } + } - // Reject the request if the application is not allowed to use the authorization code grant. - if (context.Request.IsAuthorizationCodeFlow() && - !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.AuthorizationCode)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6049), context.ClientId); + // If at least one scope was not recognized, return an error. + if (scopes.Count != 0) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6047), scopes); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2047), - uri: SR.FormatID8000(SR.ID2047)); + context.Reject( + error: Errors.InvalidScope, + description: SR.FormatID2052(Parameters.Scope), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } + } + } - // Reject the request if the application is not allowed to use the implicit grant. - if (context.Request.IsImplicitFlow() && - !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.Implicit)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6050), context.ClientId); + /// + /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications. + /// Note: this handler is not used when the degraded mode is enabled or when endpoint permissions are disabled. + /// + public class ValidateEndpointPermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2048), - uri: SR.FormatID8000(SR.ID2048)); + public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - return; - } + public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; - // Reject the request if the application is not allowed to use the authorization code/implicit grants. - if (context.Request.IsHybridFlow() && - (!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.AuthorizationCode) || - !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.Implicit))) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6051), context.ClientId); + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateScopes.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2049), - uri: SR.FormatID8000(SR.ID2049)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - return; - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - // Reject the request if the offline_access scope was request and - // if the application is not allowed to use the refresh token grant. - if (context.Request.HasScope(Scopes.OfflineAccess) && - !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.RefreshToken)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6052), context.ClientId, Scopes.OfflineAccess); + // Reject the request if the application is not allowed to use the authorization endpoint. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Authorization)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6048), context.ClientId); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2065(Scopes.OfflineAccess), - uri: SR.FormatID8000(SR.ID2065)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2046), + uri: SR.FormatID8000(SR.ID2046)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications. + /// Note: this handler is not used when the degraded mode is enabled or when grant type permissions are disabled. + /// + public class ValidateGrantTypePermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateGrantTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateGrantTypePermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications. - /// Note: this handler is not used when the degraded mode is enabled or when grant type permissions are disabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateResponseTypePermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateResponseTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - public ValidateResponseTypePermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } + + // Reject the request if the application is not allowed to use the authorization code grant. + if (context.Request.IsAuthorizationCodeFlow() && + !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.AuthorizationCode)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6049), context.ClientId); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2047), + uri: SR.FormatID8000(SR.ID2047)); - /// - public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + return; + } + + // Reject the request if the application is not allowed to use the implicit grant. + if (context.Request.IsImplicitFlow() && + !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.Implicit)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6050), context.ClientId); - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2048), + uri: SR.FormatID8000(SR.ID2048)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + return; + } - // Reject requests that specify a response_type for which no permission was granted. - if (!await HasPermissionAsync(context.Request.GetResponseTypes())) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6177), context.ClientId, context.Request.ResponseType); + // Reject the request if the application is not allowed to use the authorization code/implicit grants. + if (context.Request.IsHybridFlow() && + (!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.AuthorizationCode) || + !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.Implicit))) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6051), context.ClientId); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.FormatID2043(Parameters.ResponseType), - uri: SR.FormatID8000(SR.ID2043)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2049), + uri: SR.FormatID8000(SR.ID2049)); - return; - } + return; + } - async ValueTask HasPermissionAsync(IEnumerable types) - { - // Note: response type permissions are always prefixed with "rst:". - const string prefix = Permissions.Prefixes.ResponseType; + // Reject the request if the offline_access scope was request and + // if the application is not allowed to use the refresh token grant. + if (context.Request.HasScope(Scopes.OfflineAccess) && + !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.RefreshToken)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6052), context.ClientId, Scopes.OfflineAccess); - foreach (var permission in await _applicationManager.GetPermissionsAsync(application)) - { - // Ignore permissions that are not response type permissions. - if (!permission.StartsWith(prefix, StringComparison.Ordinal)) - { - continue; - } - - // Note: response types can be specified in any order. To ensure permissions are correctly - // checked even if the order differs from the one specified in the request, a HashSet is used. - var values = permission.Substring(prefix.Length, permission.Length - prefix.Length) - .Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries); - - if (values.Length != 0 && new HashSet(values, StringComparer.Ordinal).SetEquals(types)) - { - return true; - } - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2065(Scopes.OfflineAccess), + uri: SR.FormatID8000(SR.ID2065)); - return false; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications. + /// Note: this handler is not used when the degraded mode is enabled or when grant type permissions are disabled. + /// + public class ValidateResponseTypePermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateResponseTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateResponseTypePermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications. - /// Note: this handler is not used when the degraded mode is enabled or when scope permissions are disabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateScopePermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateScopePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateScopePermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateResponseTypePermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - /// - public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + // Reject requests that specify a response_type for which no permission was granted. + if (!await HasPermissionAsync(context.Request.GetResponseTypes())) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6177), context.ClientId, context.Request.ResponseType); - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.FormatID2043(Parameters.ResponseType), + uri: SR.FormatID8000(SR.ID2043)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + return; + } + + async ValueTask HasPermissionAsync(IEnumerable types) + { + // Note: response type permissions are always prefixed with "rst:". + const string prefix = Permissions.Prefixes.ResponseType; - foreach (var scope in context.Request.GetScopes()) + foreach (var permission in await _applicationManager.GetPermissionsAsync(application)) { - // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. - if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) || - string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal)) + // Ignore permissions that are not response type permissions. + if (!permission.StartsWith(prefix, StringComparison.Ordinal)) { continue; } - // Reject the request if the application is not allowed to use the iterated scope. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6052), context.ClientId, scope); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2051), - uri: SR.FormatID8000(SR.ID2051)); + // Note: response types can be specified in any order. To ensure permissions are correctly + // checked even if the order differs from the one specified in the request, a HashSet is used. + var values = permission.Substring(prefix.Length, permission.Length - prefix.Length) + .Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries); - return; + if (values.Length != 0 && new HashSet(values, StringComparer.Ordinal).SetEquals(types)) + { + return true; } } + + return false; } } + } - /// - /// Contains the logic responsible of rejecting authorization requests made by - /// applications for which proof key for code exchange (PKCE) was enforced. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler - { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications. + /// Note: this handler is not used when the degraded mode is enabled or when scope permissions are disabled. + /// + public class ValidateScopePermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - public ValidateProofKeyForCodeExchangeRequirement(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + public ValidateScopePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public ValidateScopePermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; - /// - public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateResponseTypePermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - // If a code_challenge was provided or if no authorization code is requested, the request is always - // considered valid, whether the proof key for code exchange requirement is enforced or not. - if (!string.IsNullOrEmpty(context.Request.CodeChallenge) || !context.Request.HasResponseType(ResponseTypes.Code)) - { - return; - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) + foreach (var scope in context.Request.GetScopes()) + { + // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. + if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) || + string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + continue; } - if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange)) + // Reject the request if the application is not allowed to use the iterated scope. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeChallenge); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6052), context.ClientId, scope); context.Reject( error: Errors.InvalidRequest, - description: SR.FormatID2054(Parameters.CodeChallenge), - uri: SR.FormatID8000(SR.ID2054)); + description: SR.GetResourceString(SR.ID2051), + uri: SR.FormatID8000(SR.ID2051)); return; } } } + } + + /// + /// Contains the logic responsible of rejecting authorization requests made by + /// applications for which proof key for code exchange (PKCE) was enforced. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateProofKeyForCodeExchangeRequirement(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of inferring the redirect URL - /// used to send the response back to the client application. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachRedirectUri : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.Request is null) - { - return default; - } + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var notification = context.Transaction.GetProperty( - typeof(ValidateAuthorizationRequestContext).FullName!); + // If a code_challenge was provided or if no authorization code is requested, the request is always + // considered valid, whether the proof key for code exchange requirement is enforced or not. + if (!string.IsNullOrEmpty(context.Request.CodeChallenge) || !context.Request.HasResponseType(ResponseTypes.Code)) + { + return; + } - // Note: at this stage, the validated redirect URI property may be null (e.g if an error - // is returned from the ExtractAuthorizationRequest/ValidateAuthorizationRequest events). - if (notification is not null && !notification.IsRejected) - { - context.RedirectUri = notification.RedirectUri; - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - return default; + if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeChallenge); + + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2054(Parameters.CodeChallenge), + uri: SR.FormatID8000(SR.ID2054)); + + return; } } + } + /// + /// Contains the logic responsible of inferring the redirect URL + /// used to send the response back to the client application. + /// + public class AttachRedirectUri : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of inferring the response mode - /// used to send the response back to the client application. + /// Gets the default descriptor definition assigned to this handler. /// - public class InferResponseMode : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachRedirectUri.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.Request is null) - { - return default; - } + if (context.Request is null) + { + return default; + } - context.ResponseMode = context.Request.ResponseMode; + var notification = context.Transaction.GetProperty( + typeof(ValidateAuthorizationRequestContext).FullName!); - // If the response_mode parameter was not specified, try to infer it. - if (string.IsNullOrEmpty(context.ResponseMode) && !string.IsNullOrEmpty(context.RedirectUri)) - { - context.ResponseMode = context.Request.IsFormPostResponseMode() ? ResponseModes.FormPost : - context.Request.IsFragmentResponseMode() ? ResponseModes.Fragment : - context.Request.IsQueryResponseMode() ? ResponseModes.Query : null; - } + // Note: at this stage, the validated redirect URI property may be null (e.g if an error + // is returned from the ExtractAuthorizationRequest/ValidateAuthorizationRequest events). + if (notification is not null && !notification.IsRejected) + { + context.RedirectUri = notification.RedirectUri; + } + + return default; + } + } + + /// + /// Contains the logic responsible of inferring the response mode + /// used to send the response back to the client application. + /// + public class InferResponseMode : IOpenIddictServerHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachRedirectUri.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Request is null) + { return default; } + + context.ResponseMode = context.Request.ResponseMode; + + // If the response_mode parameter was not specified, try to infer it. + if (string.IsNullOrEmpty(context.ResponseMode) && !string.IsNullOrEmpty(context.RedirectUri)) + { + context.ResponseMode = context.Request.IsFormPostResponseMode() ? ResponseModes.FormPost : + context.Request.IsFragmentResponseMode() ? ResponseModes.Fragment : + context.Request.IsQueryResponseMode() ? ResponseModes.Query : null; + } + + return default; } + } + /// + /// Contains the logic responsible of attaching the state to the response. + /// + public class AttachResponseState : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the state to the response. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachResponseState : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(InferResponseMode.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(InferResponseMode.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyAuthorizationResponseContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Attach the request state to the authorization response. - if (string.IsNullOrEmpty(context.Response.State)) - { - context.Response.State = context.Request?.State; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - return default; + // Attach the request state to the authorization response. + if (string.IsNullOrEmpty(context.Response.State)) + { + context.Response.State = context.Request?.State; } + + return default; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs index 64b07b25..72caa6c4 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs @@ -19,1195 +19,1194 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerHandlers { - public static partial class OpenIddictServerHandlers + public static class Device { - public static class Device + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Device request top-level processing: + */ + ExtractDeviceRequest.Descriptor, + ValidateDeviceRequest.Descriptor, + HandleDeviceRequest.Descriptor, + ApplyDeviceResponse.Descriptor, + ApplyDeviceResponse.Descriptor, + ApplyDeviceResponse.Descriptor, + ApplyDeviceResponse.Descriptor, + + /* + * Device request validation: + */ + ValidateClientIdParameter.Descriptor, + ValidateScopeParameter.Descriptor, + ValidateScopes.Descriptor, + ValidateClientId.Descriptor, + ValidateClientType.Descriptor, + ValidateClientSecret.Descriptor, + ValidateEndpointPermissions.Descriptor, + ValidateGrantTypePermissions.Descriptor, + ValidateScopePermissions.Descriptor, + + /* + * Verification request top-level processing: + */ + ExtractVerificationRequest.Descriptor, + ValidateVerificationRequest.Descriptor, + HandleVerificationRequest.Descriptor, + ApplyVerificationResponse.Descriptor, + ApplyVerificationResponse.Descriptor, + ApplyVerificationResponse.Descriptor, + ApplyVerificationResponse.Descriptor, + + /* + * Verification request handling: + */ + AttachUserCodePrincipal.Descriptor); + + /// + /// Contains the logic responsible of extracting device requests and invoking the corresponding event handlers. + /// + public class ExtractDeviceRequest : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Device request top-level processing: - */ - ExtractDeviceRequest.Descriptor, - ValidateDeviceRequest.Descriptor, - HandleDeviceRequest.Descriptor, - ApplyDeviceResponse.Descriptor, - ApplyDeviceResponse.Descriptor, - ApplyDeviceResponse.Descriptor, - ApplyDeviceResponse.Descriptor, - - /* - * Device request validation: - */ - ValidateClientIdParameter.Descriptor, - ValidateScopeParameter.Descriptor, - ValidateScopes.Descriptor, - ValidateClientId.Descriptor, - ValidateClientType.Descriptor, - ValidateClientSecret.Descriptor, - ValidateEndpointPermissions.Descriptor, - ValidateGrantTypePermissions.Descriptor, - ValidateScopePermissions.Descriptor, - - /* - * Verification request top-level processing: - */ - ExtractVerificationRequest.Descriptor, - ValidateVerificationRequest.Descriptor, - HandleVerificationRequest.Descriptor, - ApplyVerificationResponse.Descriptor, - ApplyVerificationResponse.Descriptor, - ApplyVerificationResponse.Descriptor, - ApplyVerificationResponse.Descriptor, - - /* - * Verification request handling: - */ - AttachUserCodePrincipal.Descriptor); + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractDeviceRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting device requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractDeviceRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractDeviceRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ExtractDeviceRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ExtractDeviceRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0031)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6054), notification.Request); + if (notification.Request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0031)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6054), notification.Request); } + } + + /// + /// Contains the logic responsible of validating device requests and invoking the corresponding event handlers. + /// + public class ValidateDeviceRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateDeviceRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating device requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateDeviceRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractDeviceRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateDeviceRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractDeviceRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ValidateDeviceRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ValidateDeviceRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6055)); + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6055)); } + } + + /// + /// Contains the logic responsible of handling device requests and invoking the corresponding event handlers. + /// + public class HandleDeviceRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleDeviceRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling device requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleDeviceRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateDeviceRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleDeviceRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateDeviceRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var notification = new HandleDeviceRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new HandleDeviceRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - if (notification.Principal is null) - { - // Note: no authentication type is deliberately specified to represent an unauthenticated identity. - var principal = new ClaimsPrincipal(new ClaimsIdentity()); - principal.SetScopes(notification.Request.GetScopes()); + if (notification.Principal is null) + { + // Note: no authentication type is deliberately specified to represent an unauthenticated identity. + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + principal.SetScopes(notification.Request.GetScopes()); - notification.Principal = principal; - } + notification.Principal = principal; + } - var @event = new ProcessSignInContext(context.Transaction) - { - Principal = notification.Principal, - Response = new OpenIddictResponse() - }; + var @event = new ProcessSignInContext(context.Transaction) + { + Principal = notification.Principal, + Response = new OpenIddictResponse() + }; - if (notification.Parameters.Count > 0) + if (notification.Parameters.Count > 0) + { + foreach (var parameter in notification.Parameters) { - foreach (var parameter in notification.Parameters) - { - @event.Parameters.Add(parameter.Key, parameter.Value); - } + @event.Parameters.Add(parameter.Key, parameter.Value); } + } - await _dispatcher.DispatchAsync(@event); + await _dispatcher.DispatchAsync(@event); - if (@event.IsRequestHandled) - { - context.HandleRequest(); - return; - } + if (@event.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (@event.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + else if (@event.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - else if (@event.IsRejected) - { - context.Reject( - error: @event.Error ?? Errors.InvalidGrant, - description: @event.ErrorDescription, - uri: @event.ErrorUri); - return; - } + else if (@event.IsRejected) + { + context.Reject( + error: @event.Error ?? Errors.InvalidGrant, + description: @event.ErrorDescription, + uri: @event.ErrorUri); + return; } } + } + + /// + /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// + public class ApplyDeviceResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ApplyDeviceResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyDeviceResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyDeviceResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ApplyDeviceResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ApplyDeviceResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0033)); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0033)); } + } + /// + /// Contains the logic responsible of rejecting device requests that don't specify a client identifier. + /// + public class ValidateClientIdParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting device requests that don't specify a client identifier. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientIdParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateDeviceRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateDeviceRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // client_id is a required parameter and MUST cause an error when missing. - // See https://tools.ietf.org/html/rfc8628#section-3.1 for more information. - if (string.IsNullOrEmpty(context.ClientId)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6056)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2029(Parameters.ClientId), - uri: SR.FormatID8000(SR.ID2029)); + // client_id is a required parameter and MUST cause an error when missing. + // See https://tools.ietf.org/html/rfc8628#section-3.1 for more information. + if (string.IsNullOrEmpty(context.ClientId)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6056)); - return default; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2029(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting device requests that don't specify a valid scope parameter. + /// + public class ValidateScopeParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting device requests that don't specify a valid scope parameter. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateScopeParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateDeviceRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateDeviceRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject device requests that specify scope=offline_access if the refresh token flow is not enabled. - if (context.Request.HasScope(Scopes.OfflineAccess) && !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken)) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2035(Scopes.OfflineAccess), - uri: SR.FormatID8000(SR.ID2035)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - return default; - } + // Reject device requests that specify scope=offline_access if the refresh token flow is not enabled. + if (context.Request.HasScope(Scopes.OfflineAccess) && !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken)) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2035(Scopes.OfflineAccess), + uri: SR.FormatID8000(SR.ID2035)); return default; } + + return default; } + } + + /// + /// Contains the logic responsible of rejecting authorization requests that use unregistered scopes. + /// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled. + /// + public class ValidateScopes : IOpenIddictServerHandler + { + private readonly IOpenIddictScopeManager? _scopeManager; + + public ValidateScopes(IOpenIddictScopeManager? scopeManager = null) + => _scopeManager = scopeManager; /// - /// Contains the logic responsible of rejecting authorization requests that use unregistered scopes. - /// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateScopes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler(static provider => + { + // Note: the scope manager is only resolved if the degraded mode was not enabled to ensure + // invalid core configuration exceptions are not thrown even if the managers were registered. + var options = provider.GetRequiredService>().CurrentValue; + + return options.EnableDegradedMode ? + new ValidateScopes() : + new ValidateScopes(provider.GetService() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0016))); + }) + .SetOrder(ValidateScopeParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateDeviceRequestContext context) { - private readonly IOpenIddictScopeManager? _scopeManager; - - public ValidateScopes(IOpenIddictScopeManager? scopeManager = null) - => _scopeManager = scopeManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler(static provider => - { - // Note: the scope manager is only resolved if the degraded mode was not enabled to ensure - // invalid core configuration exceptions are not thrown even if the managers were registered. - var options = provider.GetRequiredService>().CurrentValue; - - return options.EnableDegradedMode ? - new ValidateScopes() : - new ValidateScopes(provider.GetService() ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0016))); - }) - .SetOrder(ValidateScopeParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateDeviceRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If all the specified scopes are registered in the options, avoid making a database lookup. - var scopes = new HashSet(context.Request.GetScopes(), StringComparer.Ordinal); - scopes.ExceptWith(context.Options.Scopes); + // If all the specified scopes are registered in the options, avoid making a database lookup. + var scopes = new HashSet(context.Request.GetScopes(), StringComparer.Ordinal); + scopes.ExceptWith(context.Options.Scopes); - // Note: the remaining scopes are only checked if the degraded mode was not enabled, - // as this requires using the scope manager, which is never used with the degraded mode, - // even if the service was registered and resolved from the dependency injection container. - if (scopes.Count != 0 && !context.Options.EnableDegradedMode) + // Note: the remaining scopes are only checked if the degraded mode was not enabled, + // as this requires using the scope manager, which is never used with the degraded mode, + // even if the service was registered and resolved from the dependency injection container. + if (scopes.Count != 0 && !context.Options.EnableDegradedMode) + { + if (_scopeManager is null) { - if (_scopeManager is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + } - await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray())) + await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray())) + { + var name = await _scopeManager.GetNameAsync(scope); + if (!string.IsNullOrEmpty(name)) { - var name = await _scopeManager.GetNameAsync(scope); - if (!string.IsNullOrEmpty(name)) - { - scopes.Remove(name); - } + scopes.Remove(name); } } + } - // If at least one scope was not recognized, return an error. - if (scopes.Count != 0) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6057), scopes); + // If at least one scope was not recognized, return an error. + if (scopes.Count != 0) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6057), scopes); - context.Reject( - error: Errors.InvalidScope, - description: SR.FormatID2052(Parameters.Scope), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidScope, + description: SR.FormatID2052(Parameters.Scope), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; } } + } - /// - /// Contains the logic responsible of rejecting device requests that use an invalid client_id. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateClientId : IOpenIddictServerHandler - { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of rejecting device requests that use an invalid client_id. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientId : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - public ValidateClientId(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateScopes.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public ValidateClientId(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; - /// - public async ValueTask HandleAsync(ValidateDeviceRequestContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateScopes.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateDeviceRequestContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - // Retrieve the application details corresponding to the requested client_id. - // If no entity can be found, this likely indicates that the client_id is invalid. - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6058), context.ClientId); + // Retrieve the application details corresponding to the requested client_id. + // If no entity can be found, this likely indicates that the client_id is invalid. + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6058), context.ClientId); - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2052(Parameters.ClientId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2052(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting device requests made by applications + /// whose client type is not compatible with the requested grant type. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientType : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateClientType(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting device requests made by applications - /// whose client type is not compatible with the requested grant type. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientType : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientId.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateDeviceRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateClientType(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientId.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - /// - public async ValueTask HandleAsync(ValidateDeviceRequestContext context) + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { - if (context is null) + // Reject device requests containing a client_secret when the client is a public application. + if (!string.IsNullOrEmpty(context.ClientSecret)) { - throw new ArgumentNullException(nameof(context)); - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6059), context.ClientId); - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2053(Parameters.ClientSecret), + uri: SR.FormatID8000(SR.ID2053)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + return; } - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) - { - // Reject device requests containing a client_secret when the client is a public application. - if (!string.IsNullOrEmpty(context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6059), context.ClientId); + return; + } - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2053(Parameters.ClientSecret), - uri: SR.FormatID8000(SR.ID2053)); + // Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks. + if (string.IsNullOrEmpty(context.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6060), context.ClientId); - return; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2054(Parameters.ClientSecret), + uri: SR.FormatID8000(SR.ID2054)); - return; - } + return; + } + } + } - // Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks. - if (string.IsNullOrEmpty(context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6060), context.ClientId); + /// + /// Contains the logic responsible of rejecting device requests specifying an invalid client secret. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientSecret : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2054(Parameters.ClientSecret), - uri: SR.FormatID8000(SR.ID2054)); + public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - return; - } - } - } + public ValidateClientSecret(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting device requests specifying an invalid client secret. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientSecret : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateDeviceRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateClientSecret(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - /// - public async ValueTask HandleAsync(ValidateDeviceRequestContext context) + // If the application is a public client, don't validate the client secret. + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return; + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6061), context.ClientId); - // If the application is a public client, don't validate the client secret. - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) - { - return; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.GetResourceString(SR.ID2055), + uri: SR.FormatID8000(SR.ID2055)); - Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret)); + return; + } + } + } - if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6061), context.ClientId); + /// + /// Contains the logic responsible of rejecting device requests made by + /// applications that haven't been granted the device endpoint permission. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateEndpointPermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - context.Reject( - error: Errors.InvalidClient, - description: SR.GetResourceString(SR.ID2055), - uri: SR.FormatID8000(SR.ID2055)); + public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - return; - } - } - } + public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting device requests made by - /// applications that haven't been granted the device endpoint permission. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateEndpointPermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientSecret.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateDeviceRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientSecret.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateDeviceRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - // Reject the request if the application is not allowed to use the device endpoint. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Device)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6062), context.ClientId); + // Reject the request if the application is not allowed to use the device endpoint. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Device)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6062), context.ClientId); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2056), - uri: SR.FormatID8000(SR.ID2056)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2056), + uri: SR.FormatID8000(SR.ID2056)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting device requests made by unauthorized applications. + /// Note: this handler is not used when the degraded mode is enabled or when grant type permissions are disabled. + /// + public class ValidateGrantTypePermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateGrantTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateGrantTypePermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting device requests made by unauthorized applications. - /// Note: this handler is not used when the degraded mode is enabled or when grant type permissions are disabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateGrantTypePermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateDeviceRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateGrantTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateGrantTypePermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - /// - public async ValueTask HandleAsync(ValidateDeviceRequestContext context) + // Reject the request if the application is not allowed to use the device code grant. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.DeviceCode)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6118), context.ClientId); - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2027), + uri: SR.FormatID8000(SR.ID2027)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + return; + } - // Reject the request if the application is not allowed to use the device code grant. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.DeviceCode)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6118), context.ClientId); + // Reject the request if the offline_access scope was request and + // if the application is not allowed to use the refresh token grant. + if (context.Request.HasScope(Scopes.OfflineAccess) && + !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.RefreshToken)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6120), context.ClientId, Scopes.OfflineAccess); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2027), - uri: SR.FormatID8000(SR.ID2027)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2065(Scopes.OfflineAccess), + uri: SR.FormatID8000(SR.ID2065)); - return; - } + return; + } + } + } - // Reject the request if the offline_access scope was request and - // if the application is not allowed to use the refresh token grant. - if (context.Request.HasScope(Scopes.OfflineAccess) && - !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.RefreshToken)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6120), context.ClientId, Scopes.OfflineAccess); + /// + /// Contains the logic responsible of rejecting device requests made by applications + /// that haven't been granted the appropriate grant type permission. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateScopePermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2065(Scopes.OfflineAccess), - uri: SR.FormatID8000(SR.ID2065)); + public ValidateScopePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - return; - } - } - } + public ValidateScopePermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting device requests made by applications - /// that haven't been granted the appropriate grant type permission. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateScopePermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateDeviceRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateScopePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateScopePermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateDeviceRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) + foreach (var scope in context.Request.GetScopes()) + { + // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. + if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) || + string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + continue; } - foreach (var scope in context.Request.GetScopes()) + // Reject the request if the application is not allowed to use the iterated scope. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope)) { - // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. - if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) || - string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal)) - { - continue; - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6063), context.ClientId, scope); - // Reject the request if the application is not allowed to use the iterated scope. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6063), context.ClientId, scope); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2051), - uri: SR.FormatID8000(SR.ID2051)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2051), + uri: SR.FormatID8000(SR.ID2051)); - return; - } + return; } } } + } + + /// + /// Contains the logic responsible of extracting verification requests and invoking the corresponding event handlers. + /// + public class ExtractVerificationRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractVerificationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting verification requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractVerificationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractVerificationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ExtractVerificationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ExtractVerificationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0034)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6064), notification.Request); + if (notification.Request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0034)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6064), notification.Request); } + } + + /// + /// Contains the logic responsible of validating verification requests and invoking the corresponding event handlers. + /// + public class ValidateVerificationRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateVerificationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating verification requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateVerificationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractVerificationRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateVerificationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractVerificationRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var notification = new ValidateVerificationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new ValidateVerificationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6065)); + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6065)); } + } + + /// + /// Contains the logic responsible of handling verification requests and invoking the corresponding event handlers. + /// + public class HandleVerificationRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleVerificationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling verification requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleVerificationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateVerificationRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleVerificationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateVerificationRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var notification = new HandleVerificationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } + + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } + + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } + + if (notification.Principal is not null) + { + var @event = new ProcessSignInContext(context.Transaction) { - throw new ArgumentNullException(nameof(context)); + Principal = notification.Principal, + Response = new OpenIddictResponse() + }; + + if (notification.Parameters.Count > 0) + { + foreach (var parameter in notification.Parameters) + { + @event.Parameters.Add(parameter.Key, parameter.Value); + } } - var notification = new HandleVerificationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + await _dispatcher.DispatchAsync(@event); - if (notification.IsRequestHandled) + if (@event.IsRequestHandled) { context.HandleRequest(); return; } - else if (notification.IsRequestSkipped) + else if (@event.IsRequestSkipped) { context.SkipRequest(); return; } - else if (notification.IsRejected) + else if (@event.IsRejected) { context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); + error: @event.Error ?? Errors.InvalidGrant, + description: @event.ErrorDescription, + uri: @event.ErrorUri); return; } + } - if (notification.Principal is not null) - { - var @event = new ProcessSignInContext(context.Transaction) - { - Principal = notification.Principal, - Response = new OpenIddictResponse() - }; - - if (notification.Parameters.Count > 0) - { - foreach (var parameter in notification.Parameters) - { - @event.Parameters.Add(parameter.Key, parameter.Value); - } - } - - await _dispatcher.DispatchAsync(@event); - - if (@event.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (@event.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0035)); + } + } - else if (@event.IsRejected) - { - context.Reject( - error: @event.Error ?? Errors.InvalidGrant, - description: @event.ErrorDescription, - uri: @event.ErrorUri); - return; - } - } + /// + /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// + public class ApplyVerificationResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; - throw new InvalidOperationException(SR.GetResourceString(SR.ID0035)); - } - } + public ApplyVerificationResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyVerificationResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyVerificationResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var notification = new ApplyVerificationResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new ApplyVerificationResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0036)); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0036)); } + } + + /// + /// Contains the logic responsible of attaching the claims principal resolved from the user code. + /// + public class AttachUserCodePrincipal : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public AttachUserCodePrincipal(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of attaching the claims principal resolved from the user code. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachUserCodePrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(HandleVerificationRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public AttachUserCodePrincipal(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseScopedHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(HandleVerificationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Note: the user_code may not be present (e.g when the user typed - // the verification_uri manually without the user code appended). - // In this case, ignore the missing token so that a view can be - // rendered by the application to ask the user to enter the code. - if (string.IsNullOrEmpty(context.Request.UserCode)) - { - return; - } + throw new ArgumentNullException(nameof(context)); + } - var notification = new ProcessAuthenticationContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + // Note: the user_code may not be present (e.g when the user typed + // the verification_uri manually without the user code appended). + // In this case, ignore the missing token so that a view can be + // rendered by the application to ask the user to enter the code. + if (string.IsNullOrEmpty(context.Request.UserCode)) + { + return; + } - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the authentication result without triggering a new authentication flow. - context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); + var notification = new ProcessAuthenticationContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the authentication result without triggering a new authentication flow. + context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - // Note: authentication errors are deliberately not flowed up to the parent context. - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - // Attach the security principal extracted from the token to the validation context. - context.Principal = notification.UserCodePrincipal; + else if (notification.IsRejected) + { + // Note: authentication errors are deliberately not flowed up to the parent context. + return; } + + // Attach the security principal extracted from the token to the validation context. + context.Principal = notification.UserCodePrincipal; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs index 836e3ceb..1a48a6d1 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs @@ -21,1267 +21,1266 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerHandlers { - public static partial class OpenIddictServerHandlers + public static class Discovery { - public static class Discovery + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Configuration request top-level processing: + */ + ExtractConfigurationRequest.Descriptor, + ValidateConfigurationRequest.Descriptor, + HandleConfigurationRequest.Descriptor, + ApplyConfigurationResponse.Descriptor, + ApplyConfigurationResponse.Descriptor, + + /* + * Configuration request handling: + */ + AttachEndpoints.Descriptor, + AttachGrantTypes.Descriptor, + AttachResponseModes.Descriptor, + AttachResponseTypes.Descriptor, + AttachClientAuthenticationMethods.Descriptor, + AttachCodeChallengeMethods.Descriptor, + AttachScopes.Descriptor, + AttachClaims.Descriptor, + AttachSubjectTypes.Descriptor, + AttachSigningAlgorithms.Descriptor, + AttachAdditionalMetadata.Descriptor, + + /* + * Cryptography request top-level processing: + */ + ExtractCryptographyRequest.Descriptor, + ValidateCryptographyRequest.Descriptor, + HandleCryptographyRequest.Descriptor, + ApplyCryptographyResponse.Descriptor, + ApplyCryptographyResponse.Descriptor, + + /* + * Cryptography request handling: + */ + AttachSigningKeys.Descriptor); + + /// + /// Contains the logic responsible of extracting configuration requests and invoking the corresponding event handlers. + /// + public class ExtractConfigurationRequest : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Configuration request top-level processing: - */ - ExtractConfigurationRequest.Descriptor, - ValidateConfigurationRequest.Descriptor, - HandleConfigurationRequest.Descriptor, - ApplyConfigurationResponse.Descriptor, - ApplyConfigurationResponse.Descriptor, - - /* - * Configuration request handling: - */ - AttachEndpoints.Descriptor, - AttachGrantTypes.Descriptor, - AttachResponseModes.Descriptor, - AttachResponseTypes.Descriptor, - AttachClientAuthenticationMethods.Descriptor, - AttachCodeChallengeMethods.Descriptor, - AttachScopes.Descriptor, - AttachClaims.Descriptor, - AttachSubjectTypes.Descriptor, - AttachSigningAlgorithms.Descriptor, - AttachAdditionalMetadata.Descriptor, - - /* - * Cryptography request top-level processing: - */ - ExtractCryptographyRequest.Descriptor, - ValidateCryptographyRequest.Descriptor, - HandleCryptographyRequest.Descriptor, - ApplyCryptographyResponse.Descriptor, - ApplyCryptographyResponse.Descriptor, - - /* - * Cryptography request handling: - */ - AttachSigningKeys.Descriptor); + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractConfigurationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting configuration requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractConfigurationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractConfigurationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var notification = new ExtractConfigurationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new ExtractConfigurationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0037)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6066), notification.Request); + if (notification.Request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0037)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6066), notification.Request); } + } + + /// + /// Contains the logic responsible of validating configuration requests and invoking the corresponding event handlers. + /// + public class ValidateConfigurationRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateConfigurationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating configuration requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateConfigurationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractConfigurationRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateConfigurationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractConfigurationRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ValidateConfigurationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ValidateConfigurationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6067)); + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6067)); } + } + + /// + /// Contains the logic responsible of handling configuration requests and invoking the corresponding event handlers. + /// + public class HandleConfigurationRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleConfigurationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling configuration requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleConfigurationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateConfigurationRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleConfigurationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateConfigurationRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new HandleConfigurationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + throw new ArgumentNullException(nameof(context)); + } - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + var notification = new HandleConfigurationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - var response = new OpenIddictResponse - { - [Metadata.Issuer] = notification.Issuer?.AbsoluteUri, - [Metadata.AuthorizationEndpoint] = notification.AuthorizationEndpoint?.AbsoluteUri, - [Metadata.TokenEndpoint] = notification.TokenEndpoint?.AbsoluteUri, - [Metadata.IntrospectionEndpoint] = notification.IntrospectionEndpoint?.AbsoluteUri, - [Metadata.EndSessionEndpoint] = notification.LogoutEndpoint?.AbsoluteUri, - [Metadata.RevocationEndpoint] = notification.RevocationEndpoint?.AbsoluteUri, - [Metadata.UserinfoEndpoint] = notification.UserinfoEndpoint?.AbsoluteUri, - [Metadata.DeviceAuthorizationEndpoint] = notification.DeviceEndpoint?.AbsoluteUri, - [Metadata.JwksUri] = notification.CryptographyEndpoint?.AbsoluteUri, - [Metadata.GrantTypesSupported] = notification.GrantTypes.ToArray(), - [Metadata.ResponseTypesSupported] = notification.ResponseTypes.ToArray(), - [Metadata.ResponseModesSupported] = notification.ResponseModes.ToArray(), - [Metadata.ScopesSupported] = notification.Scopes.ToArray(), - [Metadata.ClaimsSupported] = notification.Claims.ToArray(), - [Metadata.IdTokenSigningAlgValuesSupported] = notification.IdTokenSigningAlgorithms.ToArray(), - [Metadata.CodeChallengeMethodsSupported] = notification.CodeChallengeMethods.ToArray(), - [Metadata.SubjectTypesSupported] = notification.SubjectTypes.ToArray(), - [Metadata.TokenEndpointAuthMethodsSupported] = notification.TokenEndpointAuthenticationMethods.ToArray(), - [Metadata.IntrospectionEndpointAuthMethodsSupported] = notification.IntrospectionEndpointAuthenticationMethods.ToArray(), - [Metadata.RevocationEndpointAuthMethodsSupported] = notification.RevocationEndpointAuthenticationMethods.ToArray() - }; + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - foreach (var metadata in notification.Metadata) - { - response.SetParameter(metadata.Key, metadata.Value); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Transaction.Response = response; + var response = new OpenIddictResponse + { + [Metadata.Issuer] = notification.Issuer?.AbsoluteUri, + [Metadata.AuthorizationEndpoint] = notification.AuthorizationEndpoint?.AbsoluteUri, + [Metadata.TokenEndpoint] = notification.TokenEndpoint?.AbsoluteUri, + [Metadata.IntrospectionEndpoint] = notification.IntrospectionEndpoint?.AbsoluteUri, + [Metadata.EndSessionEndpoint] = notification.LogoutEndpoint?.AbsoluteUri, + [Metadata.RevocationEndpoint] = notification.RevocationEndpoint?.AbsoluteUri, + [Metadata.UserinfoEndpoint] = notification.UserinfoEndpoint?.AbsoluteUri, + [Metadata.DeviceAuthorizationEndpoint] = notification.DeviceEndpoint?.AbsoluteUri, + [Metadata.JwksUri] = notification.CryptographyEndpoint?.AbsoluteUri, + [Metadata.GrantTypesSupported] = notification.GrantTypes.ToArray(), + [Metadata.ResponseTypesSupported] = notification.ResponseTypes.ToArray(), + [Metadata.ResponseModesSupported] = notification.ResponseModes.ToArray(), + [Metadata.ScopesSupported] = notification.Scopes.ToArray(), + [Metadata.ClaimsSupported] = notification.Claims.ToArray(), + [Metadata.IdTokenSigningAlgValuesSupported] = notification.IdTokenSigningAlgorithms.ToArray(), + [Metadata.CodeChallengeMethodsSupported] = notification.CodeChallengeMethods.ToArray(), + [Metadata.SubjectTypesSupported] = notification.SubjectTypes.ToArray(), + [Metadata.TokenEndpointAuthMethodsSupported] = notification.TokenEndpointAuthenticationMethods.ToArray(), + [Metadata.IntrospectionEndpointAuthMethodsSupported] = notification.IntrospectionEndpointAuthenticationMethods.ToArray(), + [Metadata.RevocationEndpointAuthMethodsSupported] = notification.RevocationEndpointAuthenticationMethods.ToArray() + }; + + foreach (var metadata in notification.Metadata) + { + response.SetParameter(metadata.Key, metadata.Value); } + + context.Transaction.Response = response; } + } + + /// + /// Contains the logic responsible of processing configuration responses and invoking the corresponding event handlers. + /// + public class ApplyConfigurationResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ApplyConfigurationResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of processing configuration responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyConfigurationResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyConfigurationResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ApplyConfigurationResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ApplyConfigurationResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0272)); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0272)); } + } + /// + /// Contains the logic responsible of attaching the endpoint URLs to the provider discovery document. + /// + public class AttachEndpoints : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the endpoint URLs to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachEndpoints : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // Note: while OpenIddict allows specifying multiple endpoint addresses, the OAuth 2.0 - // and OpenID Connect discovery specifications only allow a single address per endpoint. + // Note: while OpenIddict allows specifying multiple endpoint addresses, the OAuth 2.0 + // and OpenID Connect discovery specifications only allow a single address per endpoint. - context.AuthorizationEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, - context.Options.AuthorizationEndpointUris.FirstOrDefault()); + context.AuthorizationEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, + context.Options.AuthorizationEndpointUris.FirstOrDefault()); - context.CryptographyEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, - context.Options.CryptographyEndpointUris.FirstOrDefault()); + context.CryptographyEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, + context.Options.CryptographyEndpointUris.FirstOrDefault()); - context.DeviceEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, - context.Options.DeviceEndpointUris.FirstOrDefault()); + context.DeviceEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, + context.Options.DeviceEndpointUris.FirstOrDefault()); - context.IntrospectionEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, - context.Options.IntrospectionEndpointUris.FirstOrDefault()); + context.IntrospectionEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, + context.Options.IntrospectionEndpointUris.FirstOrDefault()); - context.LogoutEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, - context.Options.LogoutEndpointUris.FirstOrDefault()); + context.LogoutEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, + context.Options.LogoutEndpointUris.FirstOrDefault()); - context.RevocationEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, - context.Options.RevocationEndpointUris.FirstOrDefault()); + context.RevocationEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, + context.Options.RevocationEndpointUris.FirstOrDefault()); - context.TokenEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, - context.Options.TokenEndpointUris.FirstOrDefault()); + context.TokenEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, + context.Options.TokenEndpointUris.FirstOrDefault()); - context.UserinfoEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, - context.Options.UserinfoEndpointUris.FirstOrDefault()); + context.UserinfoEndpoint ??= GetEndpointAbsoluteUri(context.Issuer, + context.Options.UserinfoEndpointUris.FirstOrDefault()); - return default; + return default; - static Uri? GetEndpointAbsoluteUri(Uri? issuer, Uri? endpoint) + static Uri? GetEndpointAbsoluteUri(Uri? issuer, Uri? endpoint) + { + // If the endpoint is disabled (i.e a null address is specified), return null. + if (endpoint is null) { - // If the endpoint is disabled (i.e a null address is specified), return null. - if (endpoint is null) - { - return null; - } - - // If the endpoint address is already an absolute URL, return it as-is. - if (endpoint.IsAbsoluteUri) - { - return endpoint; - } + return null; + } - // At this stage, throw an exception if the issuer cannot be retrieved. - if (issuer is null || !issuer.IsAbsoluteUri) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0023)); - } + // If the endpoint address is already an absolute URL, return it as-is. + if (endpoint.IsAbsoluteUri) + { + return endpoint; + } - // Ensure the issuer ends with a trailing slash, as it is necessary - // for Uri's constructor to correctly compute correct absolute URLs. - if (!issuer.OriginalString.EndsWith("/", StringComparison.Ordinal)) - { - issuer = new Uri(issuer.OriginalString + "/", UriKind.Absolute); - } + // At this stage, throw an exception if the issuer cannot be retrieved. + if (issuer is null || !issuer.IsAbsoluteUri) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0023)); + } - // Ensure the endpoint does not start with a leading slash, as it is necessary - // for Uri's constructor to correctly compute correct absolute URLs. - if (endpoint.OriginalString.StartsWith("/", StringComparison.Ordinal)) - { - endpoint = new Uri(endpoint.OriginalString.Substring(1, endpoint.OriginalString.Length - 1), UriKind.Relative); - } + // Ensure the issuer ends with a trailing slash, as it is necessary + // for Uri's constructor to correctly compute correct absolute URLs. + if (!issuer.OriginalString.EndsWith("/", StringComparison.Ordinal)) + { + issuer = new Uri(issuer.OriginalString + "/", UriKind.Absolute); + } - return new Uri(issuer, endpoint); + // Ensure the endpoint does not start with a leading slash, as it is necessary + // for Uri's constructor to correctly compute correct absolute URLs. + if (endpoint.OriginalString.StartsWith("/", StringComparison.Ordinal)) + { + endpoint = new Uri(endpoint.OriginalString.Substring(1, endpoint.OriginalString.Length - 1), UriKind.Relative); } + + return new Uri(issuer, endpoint); } } + } + /// + /// Contains the logic responsible of attaching the supported grant types to the provider discovery document. + /// + public class AttachGrantTypes : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the supported grant types to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachGrantTypes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachEndpoints.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachEndpoints.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.GrantTypes.UnionWith(context.Options.GrantTypes); + context.GrantTypes.UnionWith(context.Options.GrantTypes); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the supported response modes to the provider discovery document. + /// + public class AttachResponseModes : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the supported response modes to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachResponseModes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachGrantTypes.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachGrantTypes.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.ResponseModes.UnionWith(context.Options.ResponseModes); + context.ResponseModes.UnionWith(context.Options.ResponseModes); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the supported response types to the provider discovery document. + /// + public class AttachResponseTypes : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the supported response types to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachResponseTypes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachResponseModes.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachResponseModes.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.ResponseTypes.UnionWith(context.Options.ResponseTypes); + context.ResponseTypes.UnionWith(context.Options.ResponseTypes); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the supported client + /// authentication methods to the provider discovery document. + /// + public class AttachClientAuthenticationMethods : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the supported client - /// authentication methods to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachClientAuthenticationMethods : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachResponseTypes.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachResponseTypes.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.IntrospectionEndpoint is not null) - { - context.IntrospectionEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic); - context.IntrospectionEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost); - } + throw new ArgumentNullException(nameof(context)); + } - if (context.RevocationEndpoint is not null) - { - context.RevocationEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic); - context.RevocationEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost); - } + if (context.IntrospectionEndpoint is not null) + { + context.IntrospectionEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic); + context.IntrospectionEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost); + } - if (context.TokenEndpoint is not null) - { - context.TokenEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic); - context.TokenEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost); - } + if (context.RevocationEndpoint is not null) + { + context.RevocationEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic); + context.RevocationEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost); + } - return default; + if (context.TokenEndpoint is not null) + { + context.TokenEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic); + context.TokenEndpointAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretPost); } + + return default; } + } + /// + /// Contains the logic responsible of attaching the supported + /// code challenge methods to the provider discovery document. + /// + public class AttachCodeChallengeMethods : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the supported - /// code challenge methods to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachCodeChallengeMethods : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachClientAuthenticationMethods.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachClientAuthenticationMethods.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.CodeChallengeMethods.UnionWith(context.Options.CodeChallengeMethods); + context.CodeChallengeMethods.UnionWith(context.Options.CodeChallengeMethods); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the supported response types to the provider discovery document. + /// + public class AttachScopes : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the supported response types to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachScopes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachCodeChallengeMethods.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachCodeChallengeMethods.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.Scopes.UnionWith(context.Options.Scopes); + context.Scopes.UnionWith(context.Options.Scopes); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the supported claims to the provider discovery document. + /// + public class AttachClaims : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the supported claims to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachClaims : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachScopes.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachScopes.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.Claims.UnionWith(context.Options.Claims); + context.Claims.UnionWith(context.Options.Claims); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the supported subject types to the provider discovery document. + /// + public class AttachSubjectTypes : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the supported subject types to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachSubjectTypes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachClaims.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.SubjectTypes.Add(SubjectTypes.Public); + context.SubjectTypes.Add(SubjectTypes.Public); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the supported signing algorithms to the provider discovery document. + /// + public class AttachSigningAlgorithms : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the supported signing algorithms to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachSigningAlgorithms : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachSubjectTypes.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachSubjectTypes.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - foreach (var credentials in context.Options.SigningCredentials) + foreach (var credentials in context.Options.SigningCredentials) + { + // Try to resolve the JWA algorithm short name. + var algorithm = credentials.Algorithm switch { - // Try to resolve the JWA algorithm short name. - var algorithm = credentials.Algorithm switch - { #if SUPPORTS_ECDSA - SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature - => SecurityAlgorithms.EcdsaSha256, - SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature - => SecurityAlgorithms.EcdsaSha384, - SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature - => SecurityAlgorithms.EcdsaSha512, + SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature + => SecurityAlgorithms.EcdsaSha256, + SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature + => SecurityAlgorithms.EcdsaSha384, + SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature + => SecurityAlgorithms.EcdsaSha512, #endif - SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature - => SecurityAlgorithms.RsaSha256, - SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature - => SecurityAlgorithms.RsaSha384, - SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature - => SecurityAlgorithms.RsaSha512, - - SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature - => SecurityAlgorithms.RsaSsaPssSha256, - SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature - => SecurityAlgorithms.RsaSsaPssSha384, - SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature - => SecurityAlgorithms.RsaSsaPssSha512, - - _ => null - }; - - // If the algorithm cannot be resolved, ignore it. - if (string.IsNullOrEmpty(algorithm)) - { - continue; - } + SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature + => SecurityAlgorithms.RsaSha256, + SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature + => SecurityAlgorithms.RsaSha384, + SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature + => SecurityAlgorithms.RsaSha512, + + SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature + => SecurityAlgorithms.RsaSsaPssSha256, + SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature + => SecurityAlgorithms.RsaSsaPssSha384, + SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature + => SecurityAlgorithms.RsaSsaPssSha512, + + _ => null + }; - context.IdTokenSigningAlgorithms.Add(algorithm); + // If the algorithm cannot be resolved, ignore it. + if (string.IsNullOrEmpty(algorithm)) + { + continue; } - return default; + context.IdTokenSigningAlgorithms.Add(algorithm); } + + return default; } + } + /// + /// Contains the logic responsible of attaching additional metadata to the provider discovery document. + /// + public class AttachAdditionalMetadata : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching additional metadata to the provider discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachAdditionalMetadata : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachSigningAlgorithms.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachSigningAlgorithms.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // Note: the optional claims/request/request_uri parameters are not yet supported - // by OpenIddict, so "false" is returned to encourage clients not to use them. - context.Metadata[Metadata.ClaimsParameterSupported] = false; - context.Metadata[Metadata.RequestParameterSupported] = false; - context.Metadata[Metadata.RequestUriParameterSupported] = false; + // Note: the optional claims/request/request_uri parameters are not yet supported + // by OpenIddict, so "false" is returned to encourage clients not to use them. + context.Metadata[Metadata.ClaimsParameterSupported] = false; + context.Metadata[Metadata.RequestParameterSupported] = false; + context.Metadata[Metadata.RequestUriParameterSupported] = false; - return default; - } + return default; } + } + + /// + /// Contains the logic responsible of extracting cryptography requests and invoking the corresponding event handlers. + /// + public class ExtractCryptographyRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractCryptographyRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting cryptography requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractCryptographyRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractCryptographyRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ExtractCryptographyRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ExtractCryptographyRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0038)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6068), notification.Request); + if (notification.Request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0038)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6068), notification.Request); } + } + + /// + /// Contains the logic responsible of validating cryptography requests and invoking the corresponding event handlers. + /// + public class ValidateCryptographyRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateCryptographyRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating cryptography requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateCryptographyRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractCryptographyRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateCryptographyRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractCryptographyRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ValidateCryptographyRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ValidateCryptographyRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6069)); + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6069)); } + } + + /// + /// Contains the logic responsible of handling cryptography requests and invoking the corresponding event handlers. + /// + public class HandleCryptographyRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleCryptographyRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling cryptography requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleCryptographyRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateCryptographyRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleCryptographyRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateCryptographyRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var notification = new HandleCryptographyRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new HandleCryptographyRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); - writer.WriteStartArray(); + writer.WriteStartArray(); - foreach (var key in notification.Keys) + foreach (var key in notification.Keys) + { + // Ensure a key type has been provided. + // See https://tools.ietf.org/html/rfc7517#section-4.1 + if (string.IsNullOrEmpty(key.Kty)) { - // Ensure a key type has been provided. - // See https://tools.ietf.org/html/rfc7517#section-4.1 - if (string.IsNullOrEmpty(key.Kty)) - { - context.Logger.LogWarning(SR.GetResourceString(SR.ID6070), JsonWebKeyParameterNames.Kty); + context.Logger.LogWarning(SR.GetResourceString(SR.ID6070), JsonWebKeyParameterNames.Kty); - continue; - } - - writer.WriteStartObject(); - - if (!string.IsNullOrEmpty(key.Kid)) writer.WriteString(JsonWebKeyParameterNames.Kid, key.Kid); - if (!string.IsNullOrEmpty(key.Use)) writer.WriteString(JsonWebKeyParameterNames.Use, key.Use); - if (!string.IsNullOrEmpty(key.Kty)) writer.WriteString(JsonWebKeyParameterNames.Kty, key.Kty); - if (!string.IsNullOrEmpty(key.Alg)) writer.WriteString(JsonWebKeyParameterNames.Alg, key.Alg); - if (!string.IsNullOrEmpty(key.Crv)) writer.WriteString(JsonWebKeyParameterNames.Crv, key.Crv); - if (!string.IsNullOrEmpty(key.E)) writer.WriteString(JsonWebKeyParameterNames.E, key.E); - if (!string.IsNullOrEmpty(key.N)) writer.WriteString(JsonWebKeyParameterNames.N, key.N); - if (!string.IsNullOrEmpty(key.X)) writer.WriteString(JsonWebKeyParameterNames.X, key.X); - if (!string.IsNullOrEmpty(key.Y)) writer.WriteString(JsonWebKeyParameterNames.Y, key.Y); - if (!string.IsNullOrEmpty(key.X5t)) writer.WriteString(JsonWebKeyParameterNames.X5t, key.X5t); - if (!string.IsNullOrEmpty(key.X5u)) writer.WriteString(JsonWebKeyParameterNames.X5u, key.X5u); - - if (key.KeyOps.Count != 0) - { - writer.WritePropertyName(JsonWebKeyParameterNames.KeyOps); - writer.WriteStartArray(); + continue; + } - for (var index = 0; index < key.KeyOps.Count; index++) - { - writer.WriteStringValue(key.KeyOps[index]); - } + writer.WriteStartObject(); + + if (!string.IsNullOrEmpty(key.Kid)) writer.WriteString(JsonWebKeyParameterNames.Kid, key.Kid); + if (!string.IsNullOrEmpty(key.Use)) writer.WriteString(JsonWebKeyParameterNames.Use, key.Use); + if (!string.IsNullOrEmpty(key.Kty)) writer.WriteString(JsonWebKeyParameterNames.Kty, key.Kty); + if (!string.IsNullOrEmpty(key.Alg)) writer.WriteString(JsonWebKeyParameterNames.Alg, key.Alg); + if (!string.IsNullOrEmpty(key.Crv)) writer.WriteString(JsonWebKeyParameterNames.Crv, key.Crv); + if (!string.IsNullOrEmpty(key.E)) writer.WriteString(JsonWebKeyParameterNames.E, key.E); + if (!string.IsNullOrEmpty(key.N)) writer.WriteString(JsonWebKeyParameterNames.N, key.N); + if (!string.IsNullOrEmpty(key.X)) writer.WriteString(JsonWebKeyParameterNames.X, key.X); + if (!string.IsNullOrEmpty(key.Y)) writer.WriteString(JsonWebKeyParameterNames.Y, key.Y); + if (!string.IsNullOrEmpty(key.X5t)) writer.WriteString(JsonWebKeyParameterNames.X5t, key.X5t); + if (!string.IsNullOrEmpty(key.X5u)) writer.WriteString(JsonWebKeyParameterNames.X5u, key.X5u); + + if (key.KeyOps.Count != 0) + { + writer.WritePropertyName(JsonWebKeyParameterNames.KeyOps); + writer.WriteStartArray(); - writer.WriteEndArray(); + for (var index = 0; index < key.KeyOps.Count; index++) + { + writer.WriteStringValue(key.KeyOps[index]); } - if (key.X5c.Count != 0) - { - writer.WritePropertyName(JsonWebKeyParameterNames.X5c); - writer.WriteStartArray(); + writer.WriteEndArray(); + } - for (var index = 0; index < key.X5c.Count; index++) - { - writer.WriteStringValue(key.X5c[index]); - } + if (key.X5c.Count != 0) + { + writer.WritePropertyName(JsonWebKeyParameterNames.X5c); + writer.WriteStartArray(); - writer.WriteEndArray(); + for (var index = 0; index < key.X5c.Count; index++) + { + writer.WriteStringValue(key.X5c[index]); } - writer.WriteEndObject(); + writer.WriteEndArray(); } - writer.WriteEndArray(); - writer.Flush(); - stream.Seek(0L, SeekOrigin.Begin); + writer.WriteEndObject(); + } + + writer.WriteEndArray(); + writer.Flush(); + stream.Seek(0L, SeekOrigin.Begin); - using var document = JsonDocument.Parse(stream); + using var document = JsonDocument.Parse(stream); - // Note: AddParameter() is used here to ensure the mandatory "keys" node - // is returned to the caller, even if the key set doesn't expose any key. - // See https://tools.ietf.org/html/rfc7517#section-5 for more information. - var response = new OpenIddictResponse(); - response.AddParameter(Parameters.Keys, document.RootElement.Clone()); + // Note: AddParameter() is used here to ensure the mandatory "keys" node + // is returned to the caller, even if the key set doesn't expose any key. + // See https://tools.ietf.org/html/rfc7517#section-5 for more information. + var response = new OpenIddictResponse(); + response.AddParameter(Parameters.Keys, document.RootElement.Clone()); - context.Transaction.Response = response; - } + context.Transaction.Response = response; } + } + + /// + /// Contains the logic responsible of processing cryptography responses and invoking the corresponding event handlers. + /// + public class ApplyCryptographyResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ApplyCryptographyResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of processing cryptography responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyCryptographyResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyCryptographyResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var notification = new ApplyCryptographyResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new ApplyCryptographyResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0039)); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0039)); } + } + /// + /// Contains the logic responsible of attaching the signing keys to the JWKS document. + /// + public class AttachSigningKeys : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the signing keys to the JWKS document. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachSigningKeys : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleCryptographyRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleCryptographyRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - foreach (var credentials in context.Options.SigningCredentials) - { + foreach (var credentials in context.Options.SigningCredentials) + { #if SUPPORTS_ECDSA - if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) && - !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256) && - !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) && - !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) && - !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6071), credentials.Key.GetType().Name); + if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) && + !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256) && + !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) && + !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) && + !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6071), credentials.Key.GetType().Name); - continue; - } + continue; + } #else - if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) && - !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6072), credentials.Key.GetType().Name); + if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) && + !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6072), credentials.Key.GetType().Name); - continue; - } + continue; + } #endif - var key = new JsonWebKey - { - Use = JsonWebKeyUseNames.Sig, + var key = new JsonWebKey + { + Use = JsonWebKeyUseNames.Sig, - // Resolve the JWA identifier from the algorithm specified in the credentials. - Alg = credentials.Algorithm switch - { + // Resolve the JWA identifier from the algorithm specified in the credentials. + Alg = credentials.Algorithm switch + { #if SUPPORTS_ECDSA - SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature - => SecurityAlgorithms.EcdsaSha256, - SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature - => SecurityAlgorithms.EcdsaSha384, - SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature - => SecurityAlgorithms.EcdsaSha512, + SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature + => SecurityAlgorithms.EcdsaSha256, + SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature + => SecurityAlgorithms.EcdsaSha384, + SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature + => SecurityAlgorithms.EcdsaSha512, #endif - SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature - => SecurityAlgorithms.RsaSha256, - SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature - => SecurityAlgorithms.RsaSha384, - SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature - => SecurityAlgorithms.RsaSha512, - - SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature - => SecurityAlgorithms.RsaSsaPssSha256, - SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature - => SecurityAlgorithms.RsaSsaPssSha384, - SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature - => SecurityAlgorithms.RsaSsaPssSha512, - - _ => null - }, - - // Use the key identifier specified in the signing credentials. - Kid = credentials.Kid - }; + SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature + => SecurityAlgorithms.RsaSha256, + SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature + => SecurityAlgorithms.RsaSha384, + SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature + => SecurityAlgorithms.RsaSha512, - if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) || - credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256)) - { - // Note: IdentityModel 5 doesn't expose a method allowing to retrieve the underlying algorithm - // from a generic asymmetric security key. To work around this limitation, try to cast - // the security key to the built-in IdentityModel types to extract the required RSA instance. - // See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/395. + SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature + => SecurityAlgorithms.RsaSsaPssSha256, + SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature + => SecurityAlgorithms.RsaSsaPssSha384, + SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature + => SecurityAlgorithms.RsaSsaPssSha512, - var parameters = credentials.Key switch - { - X509SecurityKey { PublicKey: RSA algorithm } => algorithm.ExportParameters(includePrivateParameters: false), + _ => null + }, - RsaSecurityKey { Rsa: RSA algorithm } => algorithm.ExportParameters(includePrivateParameters: false), - RsaSecurityKey { Parameters: RSAParameters value } => value, + // Use the key identifier specified in the signing credentials. + Kid = credentials.Kid + }; - _ => (RSAParameters?) null - }; + if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) || + credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256)) + { + // Note: IdentityModel 5 doesn't expose a method allowing to retrieve the underlying algorithm + // from a generic asymmetric security key. To work around this limitation, try to cast + // the security key to the built-in IdentityModel types to extract the required RSA instance. + // See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/395. - if (parameters is null) - { - context.Logger.LogWarning(SR.GetResourceString(SR.ID6073), credentials.Key.GetType().Name); + var parameters = credentials.Key switch + { + X509SecurityKey { PublicKey: RSA algorithm } => algorithm.ExportParameters(includePrivateParameters: false), - continue; - } + RsaSecurityKey { Rsa: RSA algorithm } => algorithm.ExportParameters(includePrivateParameters: false), + RsaSecurityKey { Parameters: RSAParameters value } => value, - Debug.Assert(parameters.Value.Exponent is not null && - parameters.Value.Modulus is not null, SR.GetResourceString(SR.ID4003)); + _ => (RSAParameters?) null + }; - key.Kty = JsonWebAlgorithmsKeyTypes.RSA; + if (parameters is null) + { + context.Logger.LogWarning(SR.GetResourceString(SR.ID6073), credentials.Key.GetType().Name); - // Note: both E and N must be base64url-encoded. - // See https://tools.ietf.org/html/rfc7518#section-6.3.1.1 - key.E = Base64UrlEncoder.Encode(parameters.Value.Exponent); - key.N = Base64UrlEncoder.Encode(parameters.Value.Modulus); + continue; } + Debug.Assert(parameters.Value.Exponent is not null && + parameters.Value.Modulus is not null, SR.GetResourceString(SR.ID4003)); + + key.Kty = JsonWebAlgorithmsKeyTypes.RSA; + + // Note: both E and N must be base64url-encoded. + // See https://tools.ietf.org/html/rfc7518#section-6.3.1.1 + key.E = Base64UrlEncoder.Encode(parameters.Value.Exponent); + key.N = Base64UrlEncoder.Encode(parameters.Value.Modulus); + } + #if SUPPORTS_ECDSA - else if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) || - credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) || - credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) + else if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) || + credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) || + credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) + { + var parameters = credentials.Key switch { - var parameters = credentials.Key switch - { - X509SecurityKey { PublicKey: ECDsa algorithm } => algorithm.ExportParameters(includePrivateParameters: false), + X509SecurityKey { PublicKey: ECDsa algorithm } => algorithm.ExportParameters(includePrivateParameters: false), - ECDsaSecurityKey { ECDsa: ECDsa algorithm } => algorithm.ExportParameters(includePrivateParameters: false), + ECDsaSecurityKey { ECDsa: ECDsa algorithm } => algorithm.ExportParameters(includePrivateParameters: false), - _ => (ECParameters?) null - }; + _ => (ECParameters?) null + }; - if (parameters is null) - { - context.Logger.LogWarning(SR.GetResourceString(SR.ID6074), credentials.Key.GetType().Name); + if (parameters is null) + { + context.Logger.LogWarning(SR.GetResourceString(SR.ID6074), credentials.Key.GetType().Name); - continue; - } + continue; + } - var curve = IsCurve(parameters.Value, ECCurve.NamedCurves.nistP256) ? JsonWebKeyECTypes.P256 : - IsCurve(parameters.Value, ECCurve.NamedCurves.nistP384) ? JsonWebKeyECTypes.P384 : - IsCurve(parameters.Value, ECCurve.NamedCurves.nistP521) ? JsonWebKeyECTypes.P521 : null; + var curve = IsCurve(parameters.Value, ECCurve.NamedCurves.nistP256) ? JsonWebKeyECTypes.P256 : + IsCurve(parameters.Value, ECCurve.NamedCurves.nistP384) ? JsonWebKeyECTypes.P384 : + IsCurve(parameters.Value, ECCurve.NamedCurves.nistP521) ? JsonWebKeyECTypes.P521 : null; - if (string.IsNullOrEmpty(curve)) - { - context.Logger.LogWarning(SR.GetResourceString(SR.ID6167), credentials.Key.GetType().Name); + if (string.IsNullOrEmpty(curve)) + { + context.Logger.LogWarning(SR.GetResourceString(SR.ID6167), credentials.Key.GetType().Name); - continue; - } + continue; + } - Debug.Assert(parameters.Value.Q.X is not null && - parameters.Value.Q.Y is not null, SR.GetResourceString(SR.ID4004)); + Debug.Assert(parameters.Value.Q.X is not null && + parameters.Value.Q.Y is not null, SR.GetResourceString(SR.ID4004)); - Debug.Assert(parameters.Value.Curve.Oid is not null, SR.GetResourceString(SR.ID4011)); - Debug.Assert(parameters.Value.Curve.IsNamed, SR.GetResourceString(SR.ID4005)); + Debug.Assert(parameters.Value.Curve.Oid is not null, SR.GetResourceString(SR.ID4011)); + Debug.Assert(parameters.Value.Curve.IsNamed, SR.GetResourceString(SR.ID4005)); - key.Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve; - key.Crv = curve; + key.Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve; + key.Crv = curve; - // Note: both X and Y must be base64url-encoded. - // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2 - key.X = Base64UrlEncoder.Encode(parameters.Value.Q.X); - key.Y = Base64UrlEncoder.Encode(parameters.Value.Q.Y); - } + // Note: both X and Y must be base64url-encoded. + // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2 + key.X = Base64UrlEncoder.Encode(parameters.Value.Q.X); + key.Y = Base64UrlEncoder.Encode(parameters.Value.Q.Y); + } #endif - // If the signing key is embedded in a X.509 certificate, set - // the x5t and x5c parameters using the certificate details. - var certificate = (credentials.Key as X509SecurityKey)?.Certificate; - if (certificate is not null) - { - // x5t must be base64url-encoded. - // See https://tools.ietf.org/html/rfc7517#section-4.8 - key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash()); - - // x5t#S256 must be base64url-encoded. - // See https://tools.ietf.org/html/rfc7517#section-4.9 - key.X5tS256 = Base64UrlEncoder.Encode(GetCertificateHash(certificate, HashAlgorithmName.SHA256)); - - // Unlike E or N, the certificates contained in x5c - // must be base64-encoded and not base64url-encoded. - // See https://tools.ietf.org/html/rfc7517#section-4.7 - key.X5c.Add(Convert.ToBase64String(certificate.RawData)); - } - - context.Keys.Add(key); + // If the signing key is embedded in a X.509 certificate, set + // the x5t and x5c parameters using the certificate details. + var certificate = (credentials.Key as X509SecurityKey)?.Certificate; + if (certificate is not null) + { + // x5t must be base64url-encoded. + // See https://tools.ietf.org/html/rfc7517#section-4.8 + key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash()); + + // x5t#S256 must be base64url-encoded. + // See https://tools.ietf.org/html/rfc7517#section-4.9 + key.X5tS256 = Base64UrlEncoder.Encode(GetCertificateHash(certificate, HashAlgorithmName.SHA256)); + + // Unlike E or N, the certificates contained in x5c + // must be base64-encoded and not base64url-encoded. + // See https://tools.ietf.org/html/rfc7517#section-4.7 + key.X5c.Add(Convert.ToBase64String(certificate.RawData)); } - return default; + context.Keys.Add(key); + } -#if SUPPORTS_ECDSA - static bool IsCurve(ECParameters parameters, ECCurve curve) - { - Debug.Assert(parameters.Curve.Oid is not null, SR.GetResourceString(SR.ID4011)); - Debug.Assert(curve.Oid is not null, SR.GetResourceString(SR.ID4011)); + return default; - // Warning: on .NET Framework 4.x and .NET Core 2.1, exported ECParameters generally have - // a null OID value attached. To work around this limitation, both the raw OID values and - // the friendly names are compared to determine whether the curve is of the specified type. - if (!string.IsNullOrEmpty(parameters.Curve.Oid.Value) && !string.IsNullOrEmpty(curve.Oid.Value)) - { - return string.Equals(parameters.Curve.Oid.Value, curve.Oid.Value, StringComparison.Ordinal); - } +#if SUPPORTS_ECDSA + static bool IsCurve(ECParameters parameters, ECCurve curve) + { + Debug.Assert(parameters.Curve.Oid is not null, SR.GetResourceString(SR.ID4011)); + Debug.Assert(curve.Oid is not null, SR.GetResourceString(SR.ID4011)); - if (!string.IsNullOrEmpty(parameters.Curve.Oid.FriendlyName) && !string.IsNullOrEmpty(curve.Oid.FriendlyName)) - { - return string.Equals(parameters.Curve.Oid.FriendlyName, curve.Oid.FriendlyName, StringComparison.Ordinal); - } + // Warning: on .NET Framework 4.x and .NET Core 2.1, exported ECParameters generally have + // a null OID value attached. To work around this limitation, both the raw OID values and + // the friendly names are compared to determine whether the curve is of the specified type. + if (!string.IsNullOrEmpty(parameters.Curve.Oid.Value) && !string.IsNullOrEmpty(curve.Oid.Value)) + { + return string.Equals(parameters.Curve.Oid.Value, curve.Oid.Value, StringComparison.Ordinal); + } - Debug.Fail(SR.GetResourceString(SR.ID4012)); - return false; + if (!string.IsNullOrEmpty(parameters.Curve.Oid.FriendlyName) && !string.IsNullOrEmpty(curve.Oid.FriendlyName)) + { + return string.Equals(parameters.Curve.Oid.FriendlyName, curve.Oid.FriendlyName, StringComparison.Ordinal); } + + Debug.Fail(SR.GetResourceString(SR.ID4012)); + return false; + } #endif - static byte[] GetCertificateHash(X509Certificate2 certificate, HashAlgorithmName algorithm) - { + static byte[] GetCertificateHash(X509Certificate2 certificate, HashAlgorithmName algorithm) + { #if SUPPORTS_CERTIFICATE_HASHING_WITH_SPECIFIED_ALGORITHM - return certificate.GetCertHash(algorithm); + return certificate.GetCertHash(algorithm); #else - using var hash = CryptoConfig.CreateFromName(algorithm.Name!) as HashAlgorithm; - if (hash is null || hash is KeyedHashAlgorithm) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)); - } + using var hash = CryptoConfig.CreateFromName(algorithm.Name!) as HashAlgorithm; + if (hash is null || hash is KeyedHashAlgorithm) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0217)); + } - return hash.ComputeHash(certificate.RawData); + return hash.ComputeHash(certificate.RawData); #endif - } } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs index 356cbab5..ab5c27d2 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs @@ -26,1761 +26,1760 @@ using SR = OpenIddict.Abstractions.OpenIddictResources; using Org.BouncyCastle.Utilities; #endif -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerHandlers { - public static partial class OpenIddictServerHandlers + public static class Exchange { - public static class Exchange + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Token request top-level processing: + */ + ExtractTokenRequest.Descriptor, + ValidateTokenRequest.Descriptor, + HandleTokenRequest.Descriptor, + ApplyTokenResponse.Descriptor, + ApplyTokenResponse.Descriptor, + ApplyTokenResponse.Descriptor, + ApplyTokenResponse.Descriptor, + + /* + * Token request validation: + */ + ValidateGrantType.Descriptor, + ValidateClientIdParameter.Descriptor, + ValidateAuthorizationCodeParameter.Descriptor, + ValidateClientCredentialsParameters.Descriptor, + ValidateDeviceCodeParameter.Descriptor, + ValidateRefreshTokenParameter.Descriptor, + ValidateResourceOwnerCredentialsParameters.Descriptor, + ValidateProofKeyForCodeExchangeParameters.Descriptor, + ValidateScopes.Descriptor, + ValidateClientId.Descriptor, + ValidateClientType.Descriptor, + ValidateClientSecret.Descriptor, + ValidateEndpointPermissions.Descriptor, + ValidateGrantTypePermissions.Descriptor, + ValidateScopePermissions.Descriptor, + ValidateProofKeyForCodeExchangeRequirement.Descriptor, + ValidateToken.Descriptor, + ValidatePresenters.Descriptor, + ValidateRedirectUri.Descriptor, + ValidateCodeVerifier.Descriptor, + ValidateGrantedScopes.Descriptor, + + /* + * Token request handling: + */ + AttachPrincipal.Descriptor, + + /* + * Token response handling: + */ + NormalizeErrorResponse.Descriptor); + + /// + /// Contains the logic responsible of extracting token requests and invoking the corresponding event handlers. + /// + public class ExtractTokenRequest : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Token request top-level processing: - */ - ExtractTokenRequest.Descriptor, - ValidateTokenRequest.Descriptor, - HandleTokenRequest.Descriptor, - ApplyTokenResponse.Descriptor, - ApplyTokenResponse.Descriptor, - ApplyTokenResponse.Descriptor, - ApplyTokenResponse.Descriptor, - - /* - * Token request validation: - */ - ValidateGrantType.Descriptor, - ValidateClientIdParameter.Descriptor, - ValidateAuthorizationCodeParameter.Descriptor, - ValidateClientCredentialsParameters.Descriptor, - ValidateDeviceCodeParameter.Descriptor, - ValidateRefreshTokenParameter.Descriptor, - ValidateResourceOwnerCredentialsParameters.Descriptor, - ValidateProofKeyForCodeExchangeParameters.Descriptor, - ValidateScopes.Descriptor, - ValidateClientId.Descriptor, - ValidateClientType.Descriptor, - ValidateClientSecret.Descriptor, - ValidateEndpointPermissions.Descriptor, - ValidateGrantTypePermissions.Descriptor, - ValidateScopePermissions.Descriptor, - ValidateProofKeyForCodeExchangeRequirement.Descriptor, - ValidateToken.Descriptor, - ValidatePresenters.Descriptor, - ValidateRedirectUri.Descriptor, - ValidateCodeVerifier.Descriptor, - ValidateGrantedScopes.Descriptor, - - /* - * Token request handling: - */ - AttachPrincipal.Descriptor, - - /* - * Token response handling: - */ - NormalizeErrorResponse.Descriptor); + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractTokenRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting token requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractTokenRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractTokenRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ExtractTokenRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ExtractTokenRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0040)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6075), notification.Request); + if (notification.Request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0040)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6075), notification.Request); } + } + + /// + /// Contains the logic responsible of validating token requests and invoking the corresponding event handlers. + /// + public class ValidateTokenRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateTokenRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating token requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTokenRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractTokenRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateTokenRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractTokenRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ValidateTokenRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the principal without triggering a new validation process. - context.Transaction.SetProperty(typeof(ValidateTokenRequestContext).FullName!, notification); + var notification = new ValidateTokenRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the principal without triggering a new validation process. + context.Transaction.SetProperty(typeof(ValidateTokenRequestContext).FullName!, notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6076)); + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6076)); } + } + + /// + /// Contains the logic responsible of handling token requests and invoking the corresponding event handlers. + /// + public class HandleTokenRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleTokenRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling token requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleTokenRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateTokenRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleTokenRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateTokenRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var notification = new HandleTokenRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } + + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } + + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidGrant, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } + + if (notification.Principal is not null) + { + var @event = new ProcessSignInContext(context.Transaction) + { + Principal = notification.Principal, + Response = new OpenIddictResponse() + }; + + if (notification.Parameters.Count > 0) { - throw new ArgumentNullException(nameof(context)); + foreach (var parameter in notification.Parameters) + { + @event.Parameters.Add(parameter.Key, parameter.Value); + } } - var notification = new HandleTokenRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + await _dispatcher.DispatchAsync(@event); - if (notification.IsRequestHandled) + if (@event.IsRequestHandled) { context.HandleRequest(); return; } - else if (notification.IsRequestSkipped) + else if (@event.IsRequestSkipped) { context.SkipRequest(); return; } - else if (notification.IsRejected) + else if (@event.IsRejected) { context.Reject( - error: notification.Error ?? Errors.InvalidGrant, - description: notification.ErrorDescription, - uri: notification.ErrorUri); + error: @event.Error ?? Errors.InvalidRequest, + description: @event.ErrorDescription, + uri: @event.ErrorUri); return; } + } - if (notification.Principal is not null) - { - var @event = new ProcessSignInContext(context.Transaction) - { - Principal = notification.Principal, - Response = new OpenIddictResponse() - }; - - if (notification.Parameters.Count > 0) - { - foreach (var parameter in notification.Parameters) - { - @event.Parameters.Add(parameter.Key, parameter.Value); - } - } - - await _dispatcher.DispatchAsync(@event); - - if (@event.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (@event.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0041)); + } + } - else if (@event.IsRejected) - { - context.Reject( - error: @event.Error ?? Errors.InvalidRequest, - description: @event.ErrorDescription, - uri: @event.ErrorUri); - return; - } - } + /// + /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// + public class ApplyTokenResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; - throw new InvalidOperationException(SR.GetResourceString(SR.ID0041)); - } - } + public ApplyTokenResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyTokenResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyTokenResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ApplyTokenResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ApplyTokenResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0042)); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0042)); } + } + /// + /// Contains the logic responsible of rejecting token requests that specify an invalid grant type. + /// + public class ValidateGrantType : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that specify an invalid grant type. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateGrantType : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject token requests missing the mandatory grant_type parameter. - if (string.IsNullOrEmpty(context.Request.GrantType)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.GrantType); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.GrantType), - uri: SR.FormatID8000(SR.ID2029)); + // Reject token requests missing the mandatory grant_type parameter. + if (string.IsNullOrEmpty(context.Request.GrantType)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.GrantType); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.GrantType), + uri: SR.FormatID8000(SR.ID2029)); - // Reject token requests that don't specify a supported grant type. - if (!context.Options.GrantTypes.Contains(context.Request.GrantType)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6078), context.Request.GrantType); + return default; + } - context.Reject( - error: Errors.UnsupportedGrantType, - description: SR.FormatID2032(Parameters.GrantType), - uri: SR.FormatID8000(SR.ID2032)); + // Reject token requests that don't specify a supported grant type. + if (!context.Options.GrantTypes.Contains(context.Request.GrantType)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6078), context.Request.GrantType); - return default; - } + context.Reject( + error: Errors.UnsupportedGrantType, + description: SR.FormatID2032(Parameters.GrantType), + uri: SR.FormatID8000(SR.ID2032)); - // Reject token requests that specify scope=offline_access if the refresh token flow is not enabled. - if (context.Request.HasScope(Scopes.OfflineAccess) && - !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken)) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2035(Scopes.OfflineAccess), - uri: SR.FormatID8000(SR.ID2035)); + return default; + } - return default; - } + // Reject token requests that specify scope=offline_access if the refresh token flow is not enabled. + if (context.Request.HasScope(Scopes.OfflineAccess) && + !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken)) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2035(Scopes.OfflineAccess), + uri: SR.FormatID8000(SR.ID2035)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests that don't specify a client identifier. + /// + public class ValidateClientIdParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that don't specify a client identifier. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientIdParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateGrantType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateGrantType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (!string.IsNullOrEmpty(context.ClientId)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // At this stage, reject the token request unless the client identification requirement was disabled. - // Independently of this setting, also reject grant_type=authorization_code requests that don't specify - // a client_id, as the client identifier MUST be sent by the client application in the request body - // if it cannot be inferred from the client authentication method (e.g the username when using basic). - // See https://tools.ietf.org/html/rfc6749#section-4.1.3 for more information. - if (!context.Options.AcceptAnonymousClients || context.Request.IsAuthorizationCodeGrantType()) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.ClientId); + if (!string.IsNullOrEmpty(context.ClientId)) + { + return default; + } - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2029(Parameters.ClientId), - uri: SR.FormatID8000(SR.ID2029)); + // At this stage, reject the token request unless the client identification requirement was disabled. + // Independently of this setting, also reject grant_type=authorization_code requests that don't specify + // a client_id, as the client identifier MUST be sent by the client application in the request body + // if it cannot be inferred from the client authentication method (e.g the username when using basic). + // See https://tools.ietf.org/html/rfc6749#section-4.1.3 for more information. + if (!context.Options.AcceptAnonymousClients || context.Request.IsAuthorizationCodeGrantType()) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.ClientId); - return default; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2029(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests that don't + /// specify an authorization code for the authorization code grant type. + /// + public class ValidateAuthorizationCodeParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that don't - /// specify an authorization code for the authorization code grant type. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAuthorizationCodeParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject grant_type=authorization_code requests missing the authorization code. - // See https://tools.ietf.org/html/rfc6749#section-4.1.3 for more information. - if (context.Request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(context.Request.Code)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.Code); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.Code), - uri: SR.FormatID8000(SR.ID2029)); + // Reject grant_type=authorization_code requests missing the authorization code. + // See https://tools.ietf.org/html/rfc6749#section-4.1.3 for more information. + if (context.Request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(context.Request.Code)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.Code); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.Code), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests that don't + /// specify client credentials for the client credentials grant type. + /// + public class ValidateClientCredentialsParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that don't - /// specify client credentials for the client credentials grant type. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientCredentialsParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateAuthorizationCodeParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateAuthorizationCodeParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject grant_type=client_credentials requests missing the client credentials. - // See https://tools.ietf.org/html/rfc6749#section-4.4.1 for more information. - if (context.Request.IsClientCredentialsGrantType() && (string.IsNullOrEmpty(context.Request.ClientId) || - string.IsNullOrEmpty(context.Request.ClientSecret))) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2057(Parameters.ClientId, Parameters.ClientSecret), - uri: SR.FormatID8000(SR.ID2057)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - return default; - } + // Reject grant_type=client_credentials requests missing the client credentials. + // See https://tools.ietf.org/html/rfc6749#section-4.4.1 for more information. + if (context.Request.IsClientCredentialsGrantType() && (string.IsNullOrEmpty(context.Request.ClientId) || + string.IsNullOrEmpty(context.Request.ClientSecret))) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2057(Parameters.ClientId, Parameters.ClientSecret), + uri: SR.FormatID8000(SR.ID2057)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests that + /// don't specify a device code for the device code grant type. + /// + public class ValidateDeviceCodeParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that - /// don't specify a device code for the device code grant type. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateDeviceCodeParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateClientCredentialsParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateClientCredentialsParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject grant_type=urn:ietf:params:oauth:grant-type:device_code requests missing the device code. - // See https://tools.ietf.org/html/rfc8628#section-3.4 for more information. - if (context.Request.IsDeviceCodeGrantType() && string.IsNullOrEmpty(context.Request.DeviceCode)) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2058(Parameters.DeviceCode), - uri: SR.FormatID8000(SR.ID2058)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - return default; - } + // Reject grant_type=urn:ietf:params:oauth:grant-type:device_code requests missing the device code. + // See https://tools.ietf.org/html/rfc8628#section-3.4 for more information. + if (context.Request.IsDeviceCodeGrantType() && string.IsNullOrEmpty(context.Request.DeviceCode)) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2058(Parameters.DeviceCode), + uri: SR.FormatID8000(SR.ID2058)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests that + /// specify invalid parameters for the refresh token grant type. + /// + public class ValidateRefreshTokenParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that - /// specify invalid parameters for the refresh token grant type. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateRefreshTokenParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateDeviceCodeParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateDeviceCodeParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject grant_type=refresh_token requests missing the refresh token. - // See https://tools.ietf.org/html/rfc6749#section-6 for more information. - if (context.Request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(context.Request.RefreshToken)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.RefreshToken); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.RefreshToken), - uri: SR.FormatID8000(SR.ID2029)); + // Reject grant_type=refresh_token requests missing the refresh token. + // See https://tools.ietf.org/html/rfc6749#section-6 for more information. + if (context.Request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(context.Request.RefreshToken)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.RefreshToken); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.RefreshToken), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests + /// that specify invalid parameters for the password grant type. + /// + public class ValidateResourceOwnerCredentialsParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests - /// that specify invalid parameters for the password grant type. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateResourceOwnerCredentialsParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateRefreshTokenParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateRefreshTokenParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject grant_type=password requests missing username or password. - // See https://tools.ietf.org/html/rfc6749#section-4.3.2 for more information. - if (context.Request.IsPasswordGrantType() && (string.IsNullOrEmpty(context.Request.Username) || - string.IsNullOrEmpty(context.Request.Password))) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6079)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2059(Parameters.Username, Parameters.Password), - uri: SR.FormatID8000(SR.ID2059)); + // Reject grant_type=password requests missing username or password. + // See https://tools.ietf.org/html/rfc6749#section-4.3.2 for more information. + if (context.Request.IsPasswordGrantType() && (string.IsNullOrEmpty(context.Request.Username) || + string.IsNullOrEmpty(context.Request.Password))) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6079)); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2059(Parameters.Username, Parameters.Password), + uri: SR.FormatID8000(SR.ID2059)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests that don't specify valid PKCE parameters. + /// + public class ValidateProofKeyForCodeExchangeParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that don't specify valid PKCE parameters. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateProofKeyForCodeExchangeParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateResourceOwnerCredentialsParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateResourceOwnerCredentialsParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (!context.Request.IsAuthorizationCodeGrantType()) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Optimization: the ValidateCodeVerifier event handler automatically rejects grant_type=authorization_code - // requests missing the code_verifier parameter when a challenge was specified in the authorization request. - // That check requires decrypting the authorization code and determining whether a code challenge was set. - // If OpenIddict was configured to require PKCE, this can be potentially avoided by making an early check here. - if (context.Options.RequireProofKeyForCodeExchange && string.IsNullOrEmpty(context.Request.CodeVerifier)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeVerifier); + if (!context.Request.IsAuthorizationCodeGrantType()) + { + return default; + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.CodeVerifier), - uri: SR.FormatID8000(SR.ID2029)); + // Optimization: the ValidateCodeVerifier event handler automatically rejects grant_type=authorization_code + // requests missing the code_verifier parameter when a challenge was specified in the authorization request. + // That check requires decrypting the authorization code and determining whether a code challenge was set. + // If OpenIddict was configured to require PKCE, this can be potentially avoided by making an early check here. + if (context.Options.RequireProofKeyForCodeExchange && string.IsNullOrEmpty(context.Request.CodeVerifier)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6033), Parameters.CodeVerifier); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.CodeVerifier), + uri: SR.FormatID8000(SR.ID2029)); return default; } - } + + return default; + } + } + + /// + /// Contains the logic responsible of rejecting authorization requests that use unregistered scopes. + /// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled. + /// + public class ValidateScopes : IOpenIddictServerHandler + { + private readonly IOpenIddictScopeManager? _scopeManager; + + public ValidateScopes(IOpenIddictScopeManager? scopeManager = null) + => _scopeManager = scopeManager; /// - /// Contains the logic responsible of rejecting authorization requests that use unregistered scopes. - /// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateScopes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler(static provider => + { + // Note: the scope manager is only resolved if the degraded mode was not enabled to ensure + // invalid core configuration exceptions are not thrown even if the managers were registered. + var options = provider.GetRequiredService>().CurrentValue; + + return options.EnableDegradedMode ? + new ValidateScopes() : + new ValidateScopes(provider.GetService() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0016))); + }) + .SetOrder(ValidateProofKeyForCodeExchangeParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenRequestContext context) { - private readonly IOpenIddictScopeManager? _scopeManager; - - public ValidateScopes(IOpenIddictScopeManager? scopeManager = null) - => _scopeManager = scopeManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler(static provider => - { - // Note: the scope manager is only resolved if the degraded mode was not enabled to ensure - // invalid core configuration exceptions are not thrown even if the managers were registered. - var options = provider.GetRequiredService>().CurrentValue; - - return options.EnableDegradedMode ? - new ValidateScopes() : - new ValidateScopes(provider.GetService() ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0016))); - }) - .SetOrder(ValidateProofKeyForCodeExchangeParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If all the specified scopes are registered in the options, avoid making a database lookup. - var scopes = new HashSet(context.Request.GetScopes(), StringComparer.Ordinal); - scopes.ExceptWith(context.Options.Scopes); + // If all the specified scopes are registered in the options, avoid making a database lookup. + var scopes = new HashSet(context.Request.GetScopes(), StringComparer.Ordinal); + scopes.ExceptWith(context.Options.Scopes); - // Note: the remaining scopes are only checked if the degraded mode was not enabled, - // as this requires using the scope manager, which is never used with the degraded mode, - // even if the service was registered and resolved from the dependency injection container. - if (scopes.Count != 0 && !context.Options.EnableDegradedMode) + // Note: the remaining scopes are only checked if the degraded mode was not enabled, + // as this requires using the scope manager, which is never used with the degraded mode, + // even if the service was registered and resolved from the dependency injection container. + if (scopes.Count != 0 && !context.Options.EnableDegradedMode) + { + if (_scopeManager is null) { - if (_scopeManager is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + } - await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray())) + await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray())) + { + var name = await _scopeManager.GetNameAsync(scope); + if (!string.IsNullOrEmpty(name)) { - var name = await _scopeManager.GetNameAsync(scope); - if (!string.IsNullOrEmpty(name)) - { - scopes.Remove(name); - } + scopes.Remove(name); } } + } - // If at least one scope was not recognized, return an error. - if (scopes.Count != 0) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6080), scopes); + // If at least one scope was not recognized, return an error. + if (scopes.Count != 0) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6080), scopes); - context.Reject( - error: Errors.InvalidScope, - description: SR.FormatID2052(Parameters.Scope), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidScope, + description: SR.FormatID2052(Parameters.Scope), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; } } + } - /// - /// Contains the logic responsible of rejecting token requests that use an invalid client_id. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateClientId : IOpenIddictServerHandler - { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of rejecting token requests that use an invalid client_id. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientId : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - public ValidateClientId(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateScopes.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public ValidateClientId(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; - /// - public async ValueTask HandleAsync(ValidateTokenRequestContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateScopes.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenRequestContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - // Retrieve the application details corresponding to the requested client_id. - // If no entity can be found, this likely indicates that the client_id is invalid. - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6081), context.ClientId); + // Retrieve the application details corresponding to the requested client_id. + // If no entity can be found, this likely indicates that the client_id is invalid. + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6081), context.ClientId); - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2052(Parameters.ClientId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2052(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; } } + } - /// - /// Contains the logic responsible of rejecting token requests made by applications - /// whose client type is not compatible with the requested grant type. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateClientType : IOpenIddictServerHandler - { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of rejecting token requests made by applications + /// whose client type is not compatible with the requested grant type. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientType : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - public ValidateClientType(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientId.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public ValidateClientType(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; - /// - public async ValueTask HandleAsync(ValidateTokenRequestContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientId.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenRequestContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) + { + // Public applications are not allowed to use the client credentials grant. + if (context.Request.IsClientCredentialsGrantType()) { - // Public applications are not allowed to use the client credentials grant. - if (context.Request.IsClientCredentialsGrantType()) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6082), context.Request.ClientId); - - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.FormatID2043(Parameters.GrantType), - uri: SR.FormatID8000(SR.ID2043)); - - return; - } - - // Reject token requests containing a client_secret when the client is a public application. - if (!string.IsNullOrEmpty(context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6083), context.ClientId); - - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2053(Parameters.ClientSecret), - uri: SR.FormatID8000(SR.ID2053)); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6082), context.Request.ClientId); - return; - } + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.FormatID2043(Parameters.GrantType), + uri: SR.FormatID8000(SR.ID2043)); return; } - // Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks. - if (string.IsNullOrEmpty(context.ClientSecret)) + // Reject token requests containing a client_secret when the client is a public application. + if (!string.IsNullOrEmpty(context.ClientSecret)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6084), context.ClientId); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6083), context.ClientId); context.Reject( error: Errors.InvalidClient, - description: SR.FormatID2054(Parameters.ClientSecret), - uri: SR.FormatID8000(SR.ID2054)); + description: SR.FormatID2053(Parameters.ClientSecret), + uri: SR.FormatID8000(SR.ID2053)); return; } + + return; + } + + // Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks. + if (string.IsNullOrEmpty(context.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6084), context.ClientId); + + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2054(Parameters.ClientSecret), + uri: SR.FormatID8000(SR.ID2054)); + + return; } } + } + + /// + /// Contains the logic responsible of rejecting token requests specifying an invalid client secret. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientSecret : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateClientSecret(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting token requests specifying an invalid client secret. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientSecret : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateClientSecret(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - /// - public async ValueTask HandleAsync(ValidateTokenRequestContext context) + // If the application is a public client, don't validate the client secret. + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return; + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6085), context.ClientId); - // If the application is a public client, don't validate the client secret. - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) - { - return; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.GetResourceString(SR.ID2055), + uri: SR.FormatID8000(SR.ID2055)); - Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret)); + return; + } + } + } - if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6085), context.ClientId); + /// + /// Contains the logic responsible of rejecting token requests made by + /// applications that haven't been granted the token endpoint permission. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateEndpointPermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - context.Reject( - error: Errors.InvalidClient, - description: SR.GetResourceString(SR.ID2055), - uri: SR.FormatID8000(SR.ID2055)); + public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - return; - } - } - } + public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting token requests made by - /// applications that haven't been granted the token endpoint permission. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateEndpointPermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientSecret.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientSecret.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - // Reject the request if the application is not allowed to use the token endpoint. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6086), context.ClientId); + // Reject the request if the application is not allowed to use the token endpoint. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Token)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6086), context.ClientId); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2063), - uri: SR.FormatID8000(SR.ID2063)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2063), + uri: SR.FormatID8000(SR.ID2063)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting token requests made by applications + /// that haven't been granted the appropriate grant type permissions. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateGrantTypePermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateGrantTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateGrantTypePermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting token requests made by applications - /// that haven't been granted the appropriate grant type permissions. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateGrantTypePermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateGrantTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateGrantTypePermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - // Reject the request if the application is not allowed to use the specified grant type. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.GrantType + context.Request.GrantType)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6087), context.ClientId, context.Request.GrantType); + // Reject the request if the application is not allowed to use the specified grant type. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.GrantType + context.Request.GrantType)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6087), context.ClientId, context.Request.GrantType); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2064), - uri: SR.FormatID8000(SR.ID2064)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2064), + uri: SR.FormatID8000(SR.ID2064)); - return; - } + return; + } - // Reject the request if the offline_access scope was request and if - // the application is not allowed to use the refresh token grant type. - if (context.Request.HasScope(Scopes.OfflineAccess) && - !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.RefreshToken)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6088), context.ClientId, Scopes.OfflineAccess); + // Reject the request if the offline_access scope was request and if + // the application is not allowed to use the refresh token grant type. + if (context.Request.HasScope(Scopes.OfflineAccess) && + !await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.RefreshToken)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6088), context.ClientId, Scopes.OfflineAccess); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2065(Scopes.OfflineAccess), - uri: SR.FormatID8000(SR.ID2065)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2065(Scopes.OfflineAccess), + uri: SR.FormatID8000(SR.ID2065)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting token requests made by applications + /// that haven't been granted the appropriate grant type permission. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateScopePermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateScopePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateScopePermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting token requests made by applications - /// that haven't been granted the appropriate grant type permission. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateScopePermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateScopePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateScopePermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } + + foreach (var scope in context.Request.GetScopes()) + { + // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. + if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) || + string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + continue; } - foreach (var scope in context.Request.GetScopes()) + // Reject the request if the application is not allowed to use the iterated scope. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope)) { - // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. - if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) || - string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal)) - { - continue; - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6089), context.ClientId, scope); - // Reject the request if the application is not allowed to use the iterated scope. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6089), context.ClientId, scope); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.GetResourceString(SR.ID2051), - uri: SR.FormatID8000(SR.ID2051)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.GetResourceString(SR.ID2051), + uri: SR.FormatID8000(SR.ID2051)); - return; - } + return; } } } + } - /// - /// Contains the logic responsible of rejecting token requests made by - /// applications for which proof key for code exchange (PKCE) was enforced. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler - { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of rejecting token requests made by + /// applications for which proof key for code exchange (PKCE) was enforced. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - public ValidateProofKeyForCodeExchangeRequirement(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public ValidateProofKeyForCodeExchangeRequirement(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; - /// - public async ValueTask HandleAsync(ValidateTokenRequestContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenRequestContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - if (!context.Request.IsAuthorizationCodeGrantType()) - { - return; - } + if (!context.Request.IsAuthorizationCodeGrantType()) + { + return; + } - // If a code_verifier was provided, the request is always considered valid, - // whether the proof key for code exchange requirement is enforced or not. - if (!string.IsNullOrEmpty(context.Request.CodeVerifier)) - { - return; - } + // If a code_verifier was provided, the request is always considered valid, + // whether the proof key for code exchange requirement is enforced or not. + if (!string.IsNullOrEmpty(context.Request.CodeVerifier)) + { + return; + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.CodeVerifier); + if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.CodeVerifier); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2054(Parameters.CodeVerifier), - uri: SR.FormatID8000(SR.ID2054)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2054(Parameters.CodeVerifier), + uri: SR.FormatID8000(SR.ID2054)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting token requests that don't + /// specify a valid authorization code, device code or refresh token. + /// + public class ValidateToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of rejecting token requests that don't - /// specify a valid authorization code, device code or refresh token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseScopedHandler() - .SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateTokenRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (!context.Request.IsAuthorizationCodeGrantType() && - !context.Request.IsDeviceCodeGrantType() && - !context.Request.IsRefreshTokenGrantType()) - { - return; - } + throw new ArgumentNullException(nameof(context)); + } - var notification = new ProcessAuthenticationContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (!context.Request.IsAuthorizationCodeGrantType() && + !context.Request.IsDeviceCodeGrantType() && + !context.Request.IsRefreshTokenGrantType()) + { + return; + } - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the authentication result without triggering a new authentication flow. - context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); + var notification = new ProcessAuthenticationContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the authentication result without triggering a new authentication flow. + context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - // Attach the security principal extracted from the token to the validation context. - context.Principal = context.Request.IsAuthorizationCodeGrantType() ? notification.AuthorizationCodePrincipal : - context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal : - context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + // Attach the security principal extracted from the token to the validation context. + context.Principal = context.Request.IsAuthorizationCodeGrantType() ? notification.AuthorizationCodePrincipal : + context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal : + context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null; } + } + /// + /// Contains the logic responsible of rejecting token requests that use an authorization code, + /// a device code or a refresh token that was issued for a different client application. + /// + public class ValidatePresenters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that use an authorization code, - /// a device code or a refresh token that was issued for a different client application. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidatePresenters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (!context.Request.IsAuthorizationCodeGrantType() && - !context.Request.IsDeviceCodeGrantType() && - !context.Request.IsRefreshTokenGrantType()) - { - return default; - } + if (!context.Request.IsAuthorizationCodeGrantType() && + !context.Request.IsDeviceCodeGrantType() && + !context.Request.IsRefreshTokenGrantType()) + { + return default; + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var presenters = context.Principal.GetPresenters(); - if (presenters.IsDefaultOrEmpty) + var presenters = context.Principal.GetPresenters(); + if (presenters.IsDefaultOrEmpty) + { + // Note: presenters may be empty during a grant_type=refresh_token request if the refresh token + // was issued to a public client but cannot be null for an authorization or device code grant request. + if (context.Request.IsAuthorizationCodeGrantType()) { - // Note: presenters may be empty during a grant_type=refresh_token request if the refresh token - // was issued to a public client but cannot be null for an authorization or device code grant request. - if (context.Request.IsAuthorizationCodeGrantType()) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0043)); - } - - if (context.Request.IsDeviceCodeGrantType()) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0044)); - } - - return default; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0043)); } - // If at least one presenter was associated to the authorization code/device code/refresh token, - // reject the request if the client_id of the caller cannot be retrieved or inferred. - if (string.IsNullOrEmpty(context.ClientId)) + if (context.Request.IsDeviceCodeGrantType()) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6090)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0044)); + } - context.Reject( - error: Errors.InvalidGrant, - description: context.Request.IsAuthorizationCodeGrantType() ? SR.GetResourceString(SR.ID2066) : - context.Request.IsDeviceCodeGrantType() ? SR.GetResourceString(SR.ID2067) : - SR.GetResourceString(SR.ID2068), - uri: context.Request.IsAuthorizationCodeGrantType() ? SR.FormatID8000(SR.ID2066) : - context.Request.IsDeviceCodeGrantType() ? SR.FormatID8000(SR.ID2067) : - SR.FormatID8000(SR.ID2068)); + return default; + } - return default; - } + // If at least one presenter was associated to the authorization code/device code/refresh token, + // reject the request if the client_id of the caller cannot be retrieved or inferred. + if (string.IsNullOrEmpty(context.ClientId)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6090)); - // Ensure the authorization code/device code/refresh token was issued to the client making the token request. - // Note: when using the refresh token grant, client_id is optional but MUST be validated if present. - // See https://tools.ietf.org/html/rfc6749#section-6 - // and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken. - if (!presenters.Contains(context.ClientId)) - { - context.Logger.LogWarning(SR.GetResourceString(SR.ID6091)); + context.Reject( + error: Errors.InvalidGrant, + description: context.Request.IsAuthorizationCodeGrantType() ? SR.GetResourceString(SR.ID2066) : + context.Request.IsDeviceCodeGrantType() ? SR.GetResourceString(SR.ID2067) : + SR.GetResourceString(SR.ID2068), + uri: context.Request.IsAuthorizationCodeGrantType() ? SR.FormatID8000(SR.ID2066) : + context.Request.IsDeviceCodeGrantType() ? SR.FormatID8000(SR.ID2067) : + SR.FormatID8000(SR.ID2068)); - context.Reject( - error: Errors.InvalidGrant, - description: context.Request.IsAuthorizationCodeGrantType() ? SR.GetResourceString(SR.ID2069) : - context.Request.IsDeviceCodeGrantType() ? SR.GetResourceString(SR.ID2070) : - SR.GetResourceString(SR.ID2071), - uri: context.Request.IsAuthorizationCodeGrantType() ? SR.FormatID8000(SR.ID2069) : - context.Request.IsDeviceCodeGrantType() ? SR.FormatID8000(SR.ID2070) : - SR.FormatID8000(SR.ID2071)); + return default; + } - return default; - } + // Ensure the authorization code/device code/refresh token was issued to the client making the token request. + // Note: when using the refresh token grant, client_id is optional but MUST be validated if present. + // See https://tools.ietf.org/html/rfc6749#section-6 + // and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken. + if (!presenters.Contains(context.ClientId)) + { + context.Logger.LogWarning(SR.GetResourceString(SR.ID6091)); + + context.Reject( + error: Errors.InvalidGrant, + description: context.Request.IsAuthorizationCodeGrantType() ? SR.GetResourceString(SR.ID2069) : + context.Request.IsDeviceCodeGrantType() ? SR.GetResourceString(SR.ID2070) : + SR.GetResourceString(SR.ID2071), + uri: context.Request.IsAuthorizationCodeGrantType() ? SR.FormatID8000(SR.ID2069) : + context.Request.IsDeviceCodeGrantType() ? SR.FormatID8000(SR.ID2070) : + SR.FormatID8000(SR.ID2071)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests that specify an invalid redirect_uri. + /// + public class ValidateRedirectUri : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that specify an invalid redirect_uri. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateRedirectUri : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidatePresenters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidatePresenters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (!context.Request.IsAuthorizationCodeGrantType()) - { - return default; - } + if (!context.Request.IsAuthorizationCodeGrantType()) + { + return default; + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - - // Validate the redirect_uri sent by the client application as part of this token request. - // Note: for pure OAuth 2.0 requests, redirect_uri is only mandatory if the authorization request - // contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri - // but the specifications allow proceeding the token request without returning an error - // if the authorization request didn't contain an explicit redirect_uri. - // See https://tools.ietf.org/html/rfc6749#section-4.1.3 - // and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation. - var address = context.Principal.GetClaim(Claims.Private.RedirectUri); - if (string.IsNullOrEmpty(address)) - { - return default; - } + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + + // Validate the redirect_uri sent by the client application as part of this token request. + // Note: for pure OAuth 2.0 requests, redirect_uri is only mandatory if the authorization request + // contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri + // but the specifications allow proceeding the token request without returning an error + // if the authorization request didn't contain an explicit redirect_uri. + // See https://tools.ietf.org/html/rfc6749#section-4.1.3 + // and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation. + var address = context.Principal.GetClaim(Claims.Private.RedirectUri); + if (string.IsNullOrEmpty(address)) + { + return default; + } - if (string.IsNullOrEmpty(context.Request.RedirectUri)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.RedirectUri); + if (string.IsNullOrEmpty(context.Request.RedirectUri)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.RedirectUri); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.RedirectUri), - uri: SR.FormatID8000(SR.ID2029)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.RedirectUri), + uri: SR.FormatID8000(SR.ID2029)); - return default; - } + return default; + } - if (!string.Equals(address, context.Request.RedirectUri, StringComparison.Ordinal)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.RedirectUri); + if (!string.Equals(address, context.Request.RedirectUri, StringComparison.Ordinal)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.RedirectUri); - context.Reject( - error: Errors.InvalidGrant, - description: SR.FormatID2072(Parameters.RedirectUri), - uri: SR.FormatID8000(SR.ID2072)); - - return default; - } + context.Reject( + error: Errors.InvalidGrant, + description: SR.FormatID2072(Parameters.RedirectUri), + uri: SR.FormatID8000(SR.ID2072)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests that specify an invalid code verifier. + /// + public class ValidateCodeVerifier : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that specify an invalid code verifier. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateCodeVerifier : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateRedirectUri.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateRedirectUri.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (!context.Request.IsAuthorizationCodeGrantType()) - { - return default; - } + if (!context.Request.IsAuthorizationCodeGrantType()) + { + return default; + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // Note: the ValidateProofKeyForCodeExchangeRequirement handler (invoked earlier) ensures - // a code_verifier is specified if the proof key for code exchange requirement was enforced - // for the client application. But unlike the aforementioned handler, ValidateCodeVerifier - // is active even if the degraded mode is enabled and ensures that a code_verifier is sent if a - // code_challenge was stored in the authorization code when the authorization request was handled. + // Note: the ValidateProofKeyForCodeExchangeRequirement handler (invoked earlier) ensures + // a code_verifier is specified if the proof key for code exchange requirement was enforced + // for the client application. But unlike the aforementioned handler, ValidateCodeVerifier + // is active even if the degraded mode is enabled and ensures that a code_verifier is sent if a + // code_challenge was stored in the authorization code when the authorization request was handled. - var challenge = context.Principal.GetClaim(Claims.Private.CodeChallenge); - if (string.IsNullOrEmpty(challenge)) + var challenge = context.Principal.GetClaim(Claims.Private.CodeChallenge); + if (string.IsNullOrEmpty(challenge)) + { + // Validate that the token request does not include a code_verifier parameter + // when code_challenge private claim was attached to the authorization code. + if (!string.IsNullOrEmpty(context.Request.CodeVerifier)) { - // Validate that the token request does not include a code_verifier parameter - // when code_challenge private claim was attached to the authorization code. - if (!string.IsNullOrEmpty(context.Request.CodeVerifier)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6093), Parameters.CodeVerifier); - - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2073(Parameters.CodeVerifier, Parameters.CodeChallenge), - uri: SR.FormatID8000(SR.ID2073)); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6093), Parameters.CodeVerifier); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2073(Parameters.CodeVerifier, Parameters.CodeChallenge), + uri: SR.FormatID8000(SR.ID2073)); return default; } - // Get the code verifier from the token request. If it cannot be found, return an invalid_grant error. - if (string.IsNullOrEmpty(context.Request.CodeVerifier)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.CodeVerifier); + return default; + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.CodeVerifier), - uri: SR.FormatID8000(SR.ID2029)); + // Get the code verifier from the token request. If it cannot be found, return an invalid_grant error. + if (string.IsNullOrEmpty(context.Request.CodeVerifier)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.CodeVerifier); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.CodeVerifier), + uri: SR.FormatID8000(SR.ID2029)); - // If no code challenge method was specified, default to S256. - var method = context.Principal.GetClaim(Claims.Private.CodeChallengeMethod); - if (string.IsNullOrEmpty(method)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0268)); - } + return default; + } - // Note: when using the "plain" code challenge method, no hashing is actually performed. - // In this case, the raw ASCII bytes of the verifier are directly compared to the challenge. - byte[] data; - if (string.Equals(method, CodeChallengeMethods.Plain, StringComparison.Ordinal)) - { - data = Encoding.ASCII.GetBytes(context.Request.CodeVerifier); - } + // If no code challenge method was specified, default to S256. + var method = context.Principal.GetClaim(Claims.Private.CodeChallengeMethod); + if (string.IsNullOrEmpty(method)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0268)); + } - else if (string.Equals(method, CodeChallengeMethods.Sha256, StringComparison.Ordinal)) - { - using var algorithm = SHA256.Create(); - data = Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode( - algorithm.ComputeHash(Encoding.ASCII.GetBytes(context.Request.CodeVerifier)))); - } + // Note: when using the "plain" code challenge method, no hashing is actually performed. + // In this case, the raw ASCII bytes of the verifier are directly compared to the challenge. + byte[] data; + if (string.Equals(method, CodeChallengeMethods.Plain, StringComparison.Ordinal)) + { + data = Encoding.ASCII.GetBytes(context.Request.CodeVerifier); + } - else - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0045)); - } + else if (string.Equals(method, CodeChallengeMethods.Sha256, StringComparison.Ordinal)) + { + using var algorithm = SHA256.Create(); + data = Encoding.ASCII.GetBytes(Base64UrlEncoder.Encode( + algorithm.ComputeHash(Encoding.ASCII.GetBytes(context.Request.CodeVerifier)))); + } - // Compare the verifier and the code challenge: if the two don't match, return an error. - // Note: to prevent timing attacks, a time-constant comparer is always used. + else + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0045)); + } + + // Compare the verifier and the code challenge: if the two don't match, return an error. + // Note: to prevent timing attacks, a time-constant comparer is always used. #if SUPPORTS_TIME_CONSTANT_COMPARISONS - if (!CryptographicOperations.FixedTimeEquals(data, Encoding.ASCII.GetBytes(challenge))) + if (!CryptographicOperations.FixedTimeEquals(data, Encoding.ASCII.GetBytes(challenge))) #else - if (!Arrays.ConstantTimeAreEqual(data, Encoding.ASCII.GetBytes(challenge))) + if (!Arrays.ConstantTimeAreEqual(data, Encoding.ASCII.GetBytes(challenge))) #endif - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.CodeVerifier); + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6092), Parameters.CodeVerifier); - context.Reject( - error: Errors.InvalidGrant, - description: SR.FormatID2052(Parameters.CodeVerifier), - uri: SR.FormatID8000(SR.ID2052)); - - return default; - } + context.Reject( + error: Errors.InvalidGrant, + description: SR.FormatID2052(Parameters.CodeVerifier), + uri: SR.FormatID8000(SR.ID2052)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting token requests that specify scopes that + /// were not initially granted by the resource owner during the authorization request. + /// + public class ValidateGrantedScopes : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting token requests that specify scopes that - /// were not initially granted by the resource owner during the authorization request. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateGrantedScopes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateCodeVerifier.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateCodeVerifier.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType()) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (string.IsNullOrEmpty(context.Request.Scope)) - { - return default; - } + if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType()) + { + return default; + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + if (string.IsNullOrEmpty(context.Request.Scope)) + { + return default; + } - // When an explicit scope parameter has been included in the token request - // but was missing from the initial request, the request MUST be rejected. - // See http://tools.ietf.org/html/rfc6749#section-6 for more information. - var scopes = new HashSet(context.Principal.GetScopes(), StringComparer.Ordinal); - if (scopes.Count == 0) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6094), Parameters.Scope); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.Reject( - error: Errors.InvalidGrant, - description: SR.FormatID2074(Parameters.Scope), - uri: SR.FormatID8000(SR.ID2074)); + // When an explicit scope parameter has been included in the token request + // but was missing from the initial request, the request MUST be rejected. + // See http://tools.ietf.org/html/rfc6749#section-6 for more information. + var scopes = new HashSet(context.Principal.GetScopes(), StringComparer.Ordinal); + if (scopes.Count == 0) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6094), Parameters.Scope); - return default; - } + context.Reject( + error: Errors.InvalidGrant, + description: SR.FormatID2074(Parameters.Scope), + uri: SR.FormatID8000(SR.ID2074)); - // When an explicit scope parameter has been included in the token request, - // the authorization server MUST ensure that it doesn't contain scopes - // that were not granted during the initial authorization/token request. - // See https://tools.ietf.org/html/rfc6749#section-6 for more information. - else if (!scopes.IsSupersetOf(context.Request.GetScopes())) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6095), Parameters.Scope); + return default; + } - context.Reject( - error: Errors.InvalidGrant, - description: SR.FormatID2052(Parameters.Scope), - uri: SR.FormatID8000(SR.ID2052)); + // When an explicit scope parameter has been included in the token request, + // the authorization server MUST ensure that it doesn't contain scopes + // that were not granted during the initial authorization/token request. + // See https://tools.ietf.org/html/rfc6749#section-6 for more information. + else if (!scopes.IsSupersetOf(context.Request.GetScopes())) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6095), Parameters.Scope); - return default; - } + context.Reject( + error: Errors.InvalidGrant, + description: SR.FormatID2052(Parameters.Scope), + uri: SR.FormatID8000(SR.ID2052)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of attaching the principal extracted + /// from the authorization code/refresh token to the event context. + /// + public class AttachPrincipal : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the principal extracted - /// from the authorization code/refresh token to the event context. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachPrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleTokenRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleTokenRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType()) - { - return default; - } + if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType()) + { + return default; + } - var notification = context.Transaction.GetProperty( - typeof(ValidateTokenRequestContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); + var notification = context.Transaction.GetProperty( + typeof(ValidateTokenRequestContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); - context.Principal ??= notification.Principal; + context.Principal ??= notification.Principal; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of converting token errors to standard invalid_grant responses. + /// + public class NormalizeErrorResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of converting token errors to standard invalid_grant responses. + /// Gets the default descriptor definition assigned to this handler. /// - public class NormalizeErrorResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyTokenResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyTokenResponseContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (string.IsNullOrEmpty(context.Error)) - { - return default; - } + if (string.IsNullOrEmpty(context.Error)) + { + return default; + } - // If the error indicates an invalid token caused by an invalid authorization, - // device code or refresh token, return a standard invalid_grant. + // If the error indicates an invalid token caused by an invalid authorization, + // device code or refresh token, return a standard invalid_grant. - if (context.Request is null || !(context.Request.IsAuthorizationCodeGrantType() || - context.Request.IsDeviceCodeGrantType() || - context.Request.IsRefreshTokenGrantType())) - { - return default; - } + if (context.Request is null || !(context.Request.IsAuthorizationCodeGrantType() || + context.Request.IsDeviceCodeGrantType() || + context.Request.IsRefreshTokenGrantType())) + { + return default; + } - context.Response.Error = context.Error switch - { - // Keep "expired_token" errors as-is if the request is a device code token request. - Errors.ExpiredToken when context.Request.IsDeviceCodeGrantType() => Errors.ExpiredToken, + context.Response.Error = context.Error switch + { + // Keep "expired_token" errors as-is if the request is a device code token request. + Errors.ExpiredToken when context.Request.IsDeviceCodeGrantType() => Errors.ExpiredToken, - // Convert "invalid_token" errors to "invalid_grant". - Errors.InvalidToken => Errors.InvalidGrant, + // Convert "invalid_token" errors to "invalid_grant". + Errors.InvalidToken => Errors.InvalidGrant, - _ => context.Error // Otherwise, keep the error as-is. - }; + _ => context.Error // Otherwise, keep the error as-is. + }; - return default; - } + return default; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs index 242ad2e0..0b7c4d26 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs @@ -26,1122 +26,1121 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerHandlers { - public static partial class OpenIddictServerHandlers + public static class Introspection { - public static class Introspection + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Introspection request top-level processing: + */ + ExtractIntrospectionRequest.Descriptor, + ValidateIntrospectionRequest.Descriptor, + HandleIntrospectionRequest.Descriptor, + ApplyIntrospectionResponse.Descriptor, + ApplyIntrospectionResponse.Descriptor, + + /* + * Introspection request validation: + */ + ValidateTokenParameter.Descriptor, + ValidateClientIdParameter.Descriptor, + ValidateClientId.Descriptor, + ValidateClientType.Descriptor, + ValidateClientSecret.Descriptor, + ValidateEndpointPermissions.Descriptor, + ValidateToken.Descriptor, + ValidateTokenType.Descriptor, + ValidateAuthorizedParty.Descriptor, + + /* + * Introspection request handling: + */ + AttachPrincipal.Descriptor, + AttachMetadataClaims.Descriptor, + AttachApplicationClaims.Descriptor, + + /* + * Introspection response handling: + */ + NormalizeErrorResponse.Descriptor); + + /// + /// Contains the logic responsible of extracting introspection requests and invoking the corresponding event handlers. + /// + public class ExtractIntrospectionRequest : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Introspection request top-level processing: - */ - ExtractIntrospectionRequest.Descriptor, - ValidateIntrospectionRequest.Descriptor, - HandleIntrospectionRequest.Descriptor, - ApplyIntrospectionResponse.Descriptor, - ApplyIntrospectionResponse.Descriptor, - - /* - * Introspection request validation: - */ - ValidateTokenParameter.Descriptor, - ValidateClientIdParameter.Descriptor, - ValidateClientId.Descriptor, - ValidateClientType.Descriptor, - ValidateClientSecret.Descriptor, - ValidateEndpointPermissions.Descriptor, - ValidateToken.Descriptor, - ValidateTokenType.Descriptor, - ValidateAuthorizedParty.Descriptor, - - /* - * Introspection request handling: - */ - AttachPrincipal.Descriptor, - AttachMetadataClaims.Descriptor, - AttachApplicationClaims.Descriptor, - - /* - * Introspection response handling: - */ - NormalizeErrorResponse.Descriptor); + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractIntrospectionRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting introspection requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractIntrospectionRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractIntrospectionRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ExtractIntrospectionRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ExtractIntrospectionRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0046)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6096), notification.Request); + if (notification.Request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0046)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6096), notification.Request); } + } + + /// + /// Contains the logic responsible of validating introspection requests and invoking the corresponding event handlers. + /// + public class ValidateIntrospectionRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateIntrospectionRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating introspection requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateIntrospectionRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractIntrospectionRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateIntrospectionRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractIntrospectionRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ValidateIntrospectionRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the principal without triggering a new validation process. - context.Transaction.SetProperty(typeof(ValidateIntrospectionRequestContext).FullName!, notification); + var notification = new ValidateIntrospectionRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the principal without triggering a new validation process. + context.Transaction.SetProperty(typeof(ValidateIntrospectionRequestContext).FullName!, notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6097)); + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6097)); } + } + + /// + /// Contains the logic responsible of handling introspection requests and invoking the corresponding event handlers. + /// + public class HandleIntrospectionRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleIntrospectionRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling introspection requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleIntrospectionRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateIntrospectionRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleIntrospectionRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateIntrospectionRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new HandleIntrospectionRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new HandleIntrospectionRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } - - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - var response = new OpenIddictResponse - { - [Claims.Active] = true, - [Claims.Issuer] = notification.Issuer?.AbsoluteUri, - [Claims.Username] = notification.Username, - [Claims.Subject] = notification.Subject, - [Claims.Scope] = string.Join(" ", notification.Scopes), - [Claims.JwtId] = notification.TokenId, - [Claims.TokenType] = notification.TokenType, - [Claims.TokenUsage] = notification.TokenUsage, - [Claims.ClientId] = notification.ClientId - }; + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.IssuedAt is not null) - { - response[Claims.IssuedAt] = EpochTime.GetIntDate(notification.IssuedAt.Value.UtcDateTime); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - if (notification.NotBefore is not null) - { - response[Claims.NotBefore] = EpochTime.GetIntDate(notification.NotBefore.Value.UtcDateTime); - } + var response = new OpenIddictResponse + { + [Claims.Active] = true, + [Claims.Issuer] = notification.Issuer?.AbsoluteUri, + [Claims.Username] = notification.Username, + [Claims.Subject] = notification.Subject, + [Claims.Scope] = string.Join(" ", notification.Scopes), + [Claims.JwtId] = notification.TokenId, + [Claims.TokenType] = notification.TokenType, + [Claims.TokenUsage] = notification.TokenUsage, + [Claims.ClientId] = notification.ClientId + }; + + if (notification.IssuedAt is not null) + { + response[Claims.IssuedAt] = EpochTime.GetIntDate(notification.IssuedAt.Value.UtcDateTime); + } - if (notification.ExpiresAt is not null) - { - response[Claims.ExpiresAt] = EpochTime.GetIntDate(notification.ExpiresAt.Value.UtcDateTime); - } + if (notification.NotBefore is not null) + { + response[Claims.NotBefore] = EpochTime.GetIntDate(notification.NotBefore.Value.UtcDateTime); + } - switch (notification.Audiences.Count) - { - case 0: break; + if (notification.ExpiresAt is not null) + { + response[Claims.ExpiresAt] = EpochTime.GetIntDate(notification.ExpiresAt.Value.UtcDateTime); + } - case 1: - response[Claims.Audience] = notification.Audiences.ElementAt(0); - break; + switch (notification.Audiences.Count) + { + case 0: break; - default: - response[Claims.Audience] = notification.Audiences.ToArray(); - break; - } + case 1: + response[Claims.Audience] = notification.Audiences.ElementAt(0); + break; - foreach (var claim in notification.Claims) - { - response.SetParameter(claim.Key, claim.Value); - } + default: + response[Claims.Audience] = notification.Audiences.ToArray(); + break; + } - context.Transaction.Response = response; + foreach (var claim in notification.Claims) + { + response.SetParameter(claim.Key, claim.Value); } + + context.Transaction.Response = response; } + } + + /// + /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// + public class ApplyIntrospectionResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ApplyIntrospectionResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyIntrospectionResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyIntrospectionResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var notification = new ApplyIntrospectionResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new ApplyIntrospectionResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0047)); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0047)); } + } + /// + /// Contains the logic responsible of rejecting introspection requests that don't specify a token. + /// + public class ValidateTokenParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting introspection requests that don't specify a token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTokenParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateIntrospectionRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject introspection requests missing the mandatory token parameter. - if (string.IsNullOrEmpty(context.Request.Token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6098), Parameters.Token); + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.Token), - uri: SR.FormatID8000(SR.ID2029)); + // Reject introspection requests missing the mandatory token parameter. + if (string.IsNullOrEmpty(context.Request.Token)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6098), Parameters.Token); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.Token), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting introspection requests that don't specify a client identifier. + /// + public class ValidateClientIdParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting introspection requests that don't specify a client identifier. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientIdParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateTokenParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateIntrospectionRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateTokenParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // At this stage, reject the introspection request unless the client identification requirement was disabled. - if (!context.Options.AcceptAnonymousClients && string.IsNullOrEmpty(context.ClientId)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6098), Parameters.ClientId); + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2029(Parameters.ClientId), - uri: SR.FormatID8000(SR.ID2029)); + // At this stage, reject the introspection request unless the client identification requirement was disabled. + if (!context.Options.AcceptAnonymousClients && string.IsNullOrEmpty(context.ClientId)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6098), Parameters.ClientId); - return default; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2029(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + + /// + /// Contains the logic responsible of rejecting introspection requests that use an invalid client_id. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientId : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateClientId(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting introspection requests that use an invalid client_id. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientId : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateClientId(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - // Retrieve the application details corresponding to the requested client_id. - // If no entity can be found, this likely indicates that the client_id is invalid. - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6099), context.ClientId); + // Retrieve the application details corresponding to the requested client_id. + // If no entity can be found, this likely indicates that the client_id is invalid. + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6099), context.ClientId); - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2052(Parameters.ClientId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2052(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting introspection requests made by applications + /// whose client type is not compatible with the presence or absence of a client secret. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientType : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateClientType(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting introspection requests made by applications - /// whose client type is not compatible with the presence or absence of a client secret. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientType : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientId.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateClientType(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientId.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) + { + // Reject introspection requests containing a client_secret when the client is a public application. + if (!string.IsNullOrEmpty(context.ClientSecret)) { - // Reject introspection requests containing a client_secret when the client is a public application. - if (!string.IsNullOrEmpty(context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6100), context.ClientId); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6100), context.ClientId); - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2053(Parameters.ClientSecret), - uri: SR.FormatID8000(SR.ID2053)); - - return; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2053(Parameters.ClientSecret), + uri: SR.FormatID8000(SR.ID2053)); return; } - // Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks. - if (string.IsNullOrEmpty(context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6101), context.ClientId); + return; + } - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2054(Parameters.ClientSecret), - uri: SR.FormatID8000(SR.ID2054)); + // Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks. + if (string.IsNullOrEmpty(context.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6101), context.ClientId); - return; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2054(Parameters.ClientSecret), + uri: SR.FormatID8000(SR.ID2054)); + + return; } } + } + + /// + /// Contains the logic responsible of rejecting introspection requests specifying an invalid client secret. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientSecret : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateClientSecret(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting introspection requests specifying an invalid client secret. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientSecret : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateClientSecret(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - // If the application is a public client, don't validate the client secret. - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) - { - return; - } + // If the application is a public client, don't validate the client secret. + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) + { + return; + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret)); - if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6102), context.ClientId); + if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6102), context.ClientId); - context.Reject( - error: Errors.InvalidClient, - description: SR.GetResourceString(SR.ID2055), - uri: SR.FormatID8000(SR.ID2055)); + context.Reject( + error: Errors.InvalidClient, + description: SR.GetResourceString(SR.ID2055), + uri: SR.FormatID8000(SR.ID2055)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting introspection requests made by + /// applications that haven't been granted the introspection endpoint permission. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateEndpointPermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting introspection requests made by - /// applications that haven't been granted the introspection endpoint permission. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateEndpointPermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientSecret.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientSecret.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - // Reject the request if the application is not allowed to use the introspection endpoint. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Introspection)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6103), context.ClientId); + // Reject the request if the application is not allowed to use the introspection endpoint. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Introspection)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6103), context.ClientId); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2075), - uri: SR.FormatID8000(SR.ID2075)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2075), + uri: SR.FormatID8000(SR.ID2075)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting introspection requests that don't specify a valid token. + /// + public class ValidateToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of rejecting introspection requests that don't specify a valid token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseScopedHandler() - .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var notification = new ProcessAuthenticationContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new ProcessAuthenticationContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the authentication result without triggering a new authentication flow. - context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the authentication result without triggering a new authentication flow. + context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - // Attach the security principal extracted from the token to the validation context. - context.Principal = notification.GenericTokenPrincipal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + // Attach the security principal extracted from the token to the validation context. + context.Principal = notification.GenericTokenPrincipal; } + } + /// + /// Contains the logic responsible of rejecting introspection requests that specify an unsupported token. + /// + public class ValidateTokenType : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting introspection requests that specify an unsupported token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTokenType : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateIntrospectionRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + throw new ArgumentNullException(nameof(context)); + } - if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) && - !context.Principal.HasTokenType(TokenTypeHints.RefreshToken)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6104)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.Reject( - error: Errors.UnsupportedTokenType, - description: SR.GetResourceString(SR.ID2076), - uri: SR.FormatID8000(SR.ID2076)); + if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) && + !context.Principal.HasTokenType(TokenTypeHints.RefreshToken)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6104)); - return default; - } + context.Reject( + error: Errors.UnsupportedTokenType, + description: SR.GetResourceString(SR.ID2076), + uri: SR.FormatID8000(SR.ID2076)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting introspection requests that specify a token + /// that cannot be introspected by the client application sending the introspection requests. + /// + public class ValidateAuthorizedParty : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting introspection requests that specify a token - /// that cannot be introspected by the client application sending the introspection requests. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAuthorizedParty : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + // Note: when client identification is not enforced, this handler cannot validate + // the audiences/presenters if the client_id of the calling application is not known. + // In this case, the returned claims are limited by AttachApplicationClaims to limit exposure. + .AddFilter() + .UseSingletonHandler() + .SetOrder(ValidateTokenType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateIntrospectionRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - // Note: when client identification is not enforced, this handler cannot validate - // the audiences/presenters if the client_id of the calling application is not known. - // In this case, the returned claims are limited by AttachApplicationClaims to limit exposure. - .AddFilter() - .UseSingletonHandler() - .SetOrder(ValidateTokenType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + throw new ArgumentNullException(nameof(context)); + } - // When the introspected token is an access token, the caller must be listed either as a presenter - // (i.e the party the token was issued to) or as an audience (i.e a resource server/API). - // If the access token doesn't contain any explicit presenter/audience, the token is assumed - // to be not specific to any resource server/client application and the check is bypassed. - if (context.Principal.HasTokenType(TokenTypeHints.AccessToken) && - context.Principal.HasClaim(Claims.Private.Audience) && !context.Principal.HasAudience(context.ClientId) && - context.Principal.HasClaim(Claims.Private.Presenter) && !context.Principal.HasPresenter(context.ClientId)) - { - context.Logger.LogWarning(SR.GetResourceString(SR.ID6106)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2077), - uri: SR.FormatID8000(SR.ID2077)); + // When the introspected token is an access token, the caller must be listed either as a presenter + // (i.e the party the token was issued to) or as an audience (i.e a resource server/API). + // If the access token doesn't contain any explicit presenter/audience, the token is assumed + // to be not specific to any resource server/client application and the check is bypassed. + if (context.Principal.HasTokenType(TokenTypeHints.AccessToken) && + context.Principal.HasClaim(Claims.Private.Audience) && !context.Principal.HasAudience(context.ClientId) && + context.Principal.HasClaim(Claims.Private.Presenter) && !context.Principal.HasPresenter(context.ClientId)) + { + context.Logger.LogWarning(SR.GetResourceString(SR.ID6106)); - return default; - } + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2077), + uri: SR.FormatID8000(SR.ID2077)); - // When the introspected token is a refresh token, the caller must be - // listed as a presenter (i.e the party the token was issued to). - // If the refresh token doesn't contain any explicit presenter, the token is - // assumed to be not specific to any client application and the check is bypassed. - if (context.Principal.HasTokenType(TokenTypeHints.RefreshToken) && - context.Principal.HasClaim(Claims.Private.Presenter) && !context.Principal.HasPresenter(context.ClientId)) - { - context.Logger.LogWarning(SR.GetResourceString(SR.ID6108)); + return default; + } - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2077), - uri: SR.FormatID8000(SR.ID2077)); + // When the introspected token is a refresh token, the caller must be + // listed as a presenter (i.e the party the token was issued to). + // If the refresh token doesn't contain any explicit presenter, the token is + // assumed to be not specific to any client application and the check is bypassed. + if (context.Principal.HasTokenType(TokenTypeHints.RefreshToken) && + context.Principal.HasClaim(Claims.Private.Presenter) && !context.Principal.HasPresenter(context.ClientId)) + { + context.Logger.LogWarning(SR.GetResourceString(SR.ID6108)); - return default; - } + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2077), + uri: SR.FormatID8000(SR.ID2077)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of attaching the principal + /// extracted from the introspected token to the event context. + /// + public class AttachPrincipal : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the principal - /// extracted from the introspected token to the event context. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachPrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleIntrospectionRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var notification = context.Transaction.GetProperty( - typeof(ValidateIntrospectionRequestContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); + var notification = context.Transaction.GetProperty( + typeof(ValidateIntrospectionRequestContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); - Debug.Assert(notification.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(notification.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.Principal ??= notification.Principal; + context.Principal ??= notification.Principal; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the metadata claims extracted from the token the event context. + /// + public class AttachMetadataClaims : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the metadata claims extracted from the token the event context. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachMetadataClaims : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachPrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleIntrospectionRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachPrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.TokenId = context.Principal.GetClaim(Claims.JwtId); - context.TokenUsage = context.Principal.GetTokenType(); - context.Subject = context.Principal.GetClaim(Claims.Subject); + context.TokenId = context.Principal.GetClaim(Claims.JwtId); + context.TokenUsage = context.Principal.GetTokenType(); + context.Subject = context.Principal.GetClaim(Claims.Subject); - context.IssuedAt = context.NotBefore = context.Principal.GetCreationDate(); - context.ExpiresAt = context.Principal.GetExpirationDate(); + context.IssuedAt = context.NotBefore = context.Principal.GetCreationDate(); + context.ExpiresAt = context.Principal.GetExpirationDate(); - // Infer the audiences/client_id from the claims stored in the security principal. - context.Audiences.UnionWith(context.Principal.GetAudiences()); - context.ClientId = context.Principal.GetClaim(Claims.ClientId) ?? - context.Principal.GetPresenters().FirstOrDefault(); + // Infer the audiences/client_id from the claims stored in the security principal. + context.Audiences.UnionWith(context.Principal.GetAudiences()); + context.ClientId = context.Principal.GetClaim(Claims.ClientId) ?? + context.Principal.GetPresenters().FirstOrDefault(); - // Note: only set "token_type" when the received token is an access token. - // See https://tools.ietf.org/html/rfc7662#section-2.2 - // and https://tools.ietf.org/html/rfc6749#section-5.1 for more information. - if (context.Principal.HasTokenType(TokenTypeHints.AccessToken)) - { - context.TokenType = TokenTypes.Bearer; - } - - return default; + // Note: only set "token_type" when the received token is an access token. + // See https://tools.ietf.org/html/rfc7662#section-2.2 + // and https://tools.ietf.org/html/rfc6749#section-5.1 for more information. + if (context.Principal.HasTokenType(TokenTypeHints.AccessToken)) + { + context.TokenType = TokenTypes.Bearer; } + + return default; } + } + + /// + /// Contains the logic responsible of attaching the application-specific claims extracted from the token the event context. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class AttachApplicationClaims : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public AttachApplicationClaims() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public AttachApplicationClaims(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of attaching the application-specific claims extracted from the token the event context. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachApplicationClaims : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(AttachMetadataClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(HandleIntrospectionRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public AttachApplicationClaims() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public AttachApplicationClaims(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(AttachMetadataClaims.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(HandleIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), SR.FormatID4000(Parameters.ClientId)); - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // Don't return application-specific claims if the token is not an access token. - if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken)) - { - return; - } + // Don't return application-specific claims if the token is not an access token. + if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken)) + { + return; + } - // Only specified audiences (that were explicitly defined as allowed resources) can access - // the sensitive application-specific claims contained in the introspected access token. - if (!context.Principal.HasAudience(context.Request.ClientId)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6105), context.Request.ClientId); + // Only specified audiences (that were explicitly defined as allowed resources) can access + // the sensitive application-specific claims contained in the introspected access token. + if (!context.Principal.HasAudience(context.Request.ClientId)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6105), context.Request.ClientId); - return; - } + return; + } + + var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } + + // Public clients are not allowed to access sensitive claims as authentication cannot be enforced. + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6107), context.Request.ClientId); + + return; + } - var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); - if (application is null) + context.Username = context.Principal.Identity.Name; + context.Scopes.UnionWith(context.Principal.GetScopes()); + + foreach (var group in context.Principal.Claims.GroupBy(claim => claim.Type)) + { + // Exclude standard claims, that are already handled via strongly-typed properties. + // Make sure to always update this list when adding new built-in claim properties. + var type = group.Key; + if (type is Claims.Audience or Claims.ExpiresAt or Claims.IssuedAt or + Claims.Issuer or Claims.NotBefore or Claims.Scope or + Claims.Subject or Claims.TokenType or Claims.TokenUsage) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + continue; } - // Public clients are not allowed to access sensitive claims as authentication cannot be enforced. - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) + // Exclude OpenIddict-specific metadata claims, that are always considered private. + if (type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6107), context.Request.ClientId); - - return; + continue; } - context.Username = context.Principal.Identity.Name; - context.Scopes.UnionWith(context.Principal.GetScopes()); - - foreach (var group in context.Principal.Claims.GroupBy(claim => claim.Type)) + var claims = group.ToList(); + context.Claims[type] = claims.Count switch { - // Exclude standard claims, that are already handled via strongly-typed properties. - // Make sure to always update this list when adding new built-in claim properties. - var type = group.Key; - if (type is Claims.Audience or Claims.ExpiresAt or Claims.IssuedAt or - Claims.Issuer or Claims.NotBefore or Claims.Scope or - Claims.Subject or Claims.TokenType or Claims.TokenUsage) - { - continue; - } + // When there's only one claim with the same type, directly + // convert the claim using the specified claim value type. + 1 => ConvertToParameter(claims[0]), - // Exclude OpenIddict-specific metadata claims, that are always considered private. - if (type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) - { - continue; - } + // When multiple claims share the same type, retrieve the underlying + // JSON values and add everything to a new unique JSON array. + _ => DeserializeElement(SerializeClaims(claims)) + }; + } - var claims = group.ToList(); - context.Claims[type] = claims.Count switch - { - // When there's only one claim with the same type, directly - // convert the claim using the specified claim value type. - 1 => ConvertToParameter(claims[0]), - - // When multiple claims share the same type, retrieve the underlying - // JSON values and add everything to a new unique JSON array. - _ => DeserializeElement(SerializeClaims(claims)) - }; - } + static OpenIddictParameter ConvertToParameter(Claim claim) => claim.ValueType switch + { + ClaimValueTypes.Boolean => bool.Parse(claim.Value), - static OpenIddictParameter ConvertToParameter(Claim claim) => claim.ValueType switch - { - ClaimValueTypes.Boolean => bool.Parse(claim.Value), + ClaimValueTypes.Integer or ClaimValueTypes.Integer32 + => int.Parse(claim.Value, CultureInfo.InvariantCulture), - ClaimValueTypes.Integer or ClaimValueTypes.Integer32 - => int.Parse(claim.Value, CultureInfo.InvariantCulture), + ClaimValueTypes.Integer64 => long.Parse(claim.Value, CultureInfo.InvariantCulture), - ClaimValueTypes.Integer64 => long.Parse(claim.Value, CultureInfo.InvariantCulture), + JsonClaimValueTypes.Json or JsonClaimValueTypes.JsonArray => DeserializeElement(claim.Value), - JsonClaimValueTypes.Json or JsonClaimValueTypes.JsonArray => DeserializeElement(claim.Value), + _ => new OpenIddictParameter(claim.Value) + }; - _ => new OpenIddictParameter(claim.Value) - }; + static JsonElement DeserializeElement(string value) + { + using var document = JsonDocument.Parse(value); + return document.RootElement.Clone(); + } - static JsonElement DeserializeElement(string value) + static string SerializeClaims(IReadOnlyList claims) + { + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { - using var document = JsonDocument.Parse(value); - return document.RootElement.Clone(); - } + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = false + }); - static string SerializeClaims(IReadOnlyList claims) - { - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = false - }); + writer.WriteStartArray(); - writer.WriteStartArray(); + for (var index = 0; index < claims.Count; index++) + { + var claim = claims[index]; - for (var index = 0; index < claims.Count; index++) + switch (claim.ValueType) { - var claim = claims[index]; - - switch (claim.ValueType) - { - case ClaimValueTypes.Boolean: - writer.WriteBooleanValue(bool.Parse(claim.Value)); - break; - - case ClaimValueTypes.Integer: - case ClaimValueTypes.Integer32: - writer.WriteNumberValue(int.Parse(claim.Value, CultureInfo.InvariantCulture)); - break; - - case ClaimValueTypes.Integer64: - writer.WriteNumberValue(long.Parse(claim.Value, CultureInfo.InvariantCulture)); - break; - - case JsonClaimValueTypes.Json: - case JsonClaimValueTypes.JsonArray: - using (var document = JsonDocument.Parse(claim.Value)) - { - document.WriteTo(writer); - } - break; - - default: - writer.WriteStringValue(claim.Value); - break; - } + case ClaimValueTypes.Boolean: + writer.WriteBooleanValue(bool.Parse(claim.Value)); + break; + + case ClaimValueTypes.Integer: + case ClaimValueTypes.Integer32: + writer.WriteNumberValue(int.Parse(claim.Value, CultureInfo.InvariantCulture)); + break; + + case ClaimValueTypes.Integer64: + writer.WriteNumberValue(long.Parse(claim.Value, CultureInfo.InvariantCulture)); + break; + + case JsonClaimValueTypes.Json: + case JsonClaimValueTypes.JsonArray: + using (var document = JsonDocument.Parse(claim.Value)) + { + document.WriteTo(writer); + } + break; + + default: + writer.WriteStringValue(claim.Value); + break; } + } - writer.WriteEndArray(); - writer.Flush(); + writer.WriteEndArray(); + writer.Flush(); - return Encoding.UTF8.GetString(stream.ToArray()); - } + return Encoding.UTF8.GetString(stream.ToArray()); } } + } + /// + /// Contains the logic responsible of converting introspection errors to standard active: false responses. + /// + public class NormalizeErrorResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of converting introspection errors to standard active: false responses. + /// Gets the default descriptor definition assigned to this handler. /// - public class NormalizeErrorResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyIntrospectionResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyIntrospectionResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (string.IsNullOrEmpty(context.Error)) - { - return default; - } + throw new ArgumentNullException(nameof(context)); + } - // If the error indicates an invalid token, remove the error details and only return active: false, - // as required by the introspection specification: https://tools.ietf.org/html/rfc7662#section-2.2. - // While this prevent the resource server from determining the root cause of the introspection failure, - // this is required to keep OpenIddict fully standard and compatible with all introspection clients. + if (string.IsNullOrEmpty(context.Error)) + { + return default; + } - if (string.Equals(context.Error, Errors.InvalidToken, StringComparison.Ordinal)) - { - context.Response.Error = null; - context.Response.ErrorDescription = null; - context.Response.ErrorUri = null; + // If the error indicates an invalid token, remove the error details and only return active: false, + // as required by the introspection specification: https://tools.ietf.org/html/rfc7662#section-2.2. + // While this prevent the resource server from determining the root cause of the introspection failure, + // this is required to keep OpenIddict fully standard and compatible with all introspection clients. - context.Response[Claims.Active] = false; - } + if (string.Equals(context.Error, Errors.InvalidToken, StringComparison.Ordinal)) + { + context.Response.Error = null; + context.Response.ErrorDescription = null; + context.Response.ErrorUri = null; - return default; + context.Response[Claims.Active] = false; } + + return default; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs index ec5e94dc..b94ee9d3 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs @@ -23,1427 +23,1426 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerHandlers { - public static partial class OpenIddictServerHandlers + public static class Protection { - public static class Protection + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Token validation: + */ + ResolveTokenValidationParameters.Descriptor, + ValidateReferenceTokenIdentifier.Descriptor, + ValidateIdentityModelToken.Descriptor, + NormalizeScopeClaims.Descriptor, + MapInternalClaims.Descriptor, + RestoreReferenceTokenProperties.Descriptor, + ValidatePrincipal.Descriptor, + ValidateExpirationDate.Descriptor, + ValidateTokenEntry.Descriptor, + ValidateAuthorizationEntry.Descriptor, + + /* + * Token generation: + */ + AttachSecurityCredentials.Descriptor, + CreateTokenEntry.Descriptor, + GenerateIdentityModelToken.Descriptor, + ConvertReferenceToken.Descriptor, + BeautifyToken.Descriptor); + + /// + /// Contains the logic responsible of resolving the validation parameters used to validate tokens. + /// + public class ResolveTokenValidationParameters : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Token validation: - */ - ResolveTokenValidationParameters.Descriptor, - ValidateReferenceTokenIdentifier.Descriptor, - ValidateIdentityModelToken.Descriptor, - NormalizeScopeClaims.Descriptor, - MapInternalClaims.Descriptor, - RestoreReferenceTokenProperties.Descriptor, - ValidatePrincipal.Descriptor, - ValidateExpirationDate.Descriptor, - ValidateTokenEntry.Descriptor, - ValidateAuthorizationEntry.Descriptor, - - /* - * Token generation: - */ - AttachSecurityCredentials.Descriptor, - CreateTokenEntry.Descriptor, - GenerateIdentityModelToken.Descriptor, - ConvertReferenceToken.Descriptor, - BeautifyToken.Descriptor); - /// - /// Contains the logic responsible of resolving the validation parameters used to validate tokens. + /// Gets the default descriptor definition assigned to this handler. /// - public class ResolveTokenValidationParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var parameters = context.Options.TokenValidationParameters.Clone(); - parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; - parameters.ValidateIssuer = !string.IsNullOrEmpty(parameters.ValidIssuer); + var parameters = context.Options.TokenValidationParameters.Clone(); + parameters.ValidIssuer ??= context.Issuer?.AbsoluteUri; + parameters.ValidateIssuer = !string.IsNullOrEmpty(parameters.ValidIssuer); - parameters.ValidTypes = context.ValidTokenTypes.Count switch - { - // If no specific token type is expected, accept all token types at this stage. - // Additional filtering can be made based on the resolved/actual token type. - 0 => null, + parameters.ValidTypes = context.ValidTokenTypes.Count switch + { + // If no specific token type is expected, accept all token types at this stage. + // Additional filtering can be made based on the resolved/actual token type. + 0 => null, - // Otherwise, map the token types to their JWT public or internal representation. - _ => context.ValidTokenTypes.SelectMany(type => type switch + // Otherwise, map the token types to their JWT public or internal representation. + _ => context.ValidTokenTypes.SelectMany(type => type switch + { + // For access tokens, both "at+jwt" and "application/at+jwt" are valid. + TokenTypeHints.AccessToken => new[] { - // For access tokens, both "at+jwt" and "application/at+jwt" are valid. - TokenTypeHints.AccessToken => new[] - { - JsonWebTokenTypes.AccessToken, - JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken - }, + JsonWebTokenTypes.AccessToken, + JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken + }, - // For identity tokens, both "JWT" and "application/jwt" are valid. - TokenTypeHints.IdToken => new[] - { - JsonWebTokenTypes.IdentityToken, - JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.IdentityToken - }, + // For identity tokens, both "JWT" and "application/jwt" are valid. + TokenTypeHints.IdToken => new[] + { + JsonWebTokenTypes.IdentityToken, + JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.IdentityToken + }, - // For authorization codes, only the short "oi_auc+jwt" form is valid. - TokenTypeHints.AuthorizationCode => new[] { JsonWebTokenTypes.Private.AuthorizationCode }, + // For authorization codes, only the short "oi_auc+jwt" form is valid. + TokenTypeHints.AuthorizationCode => new[] { JsonWebTokenTypes.Private.AuthorizationCode }, - // For device codes, only the short "oi_dvc+jwt" form is valid. - TokenTypeHints.DeviceCode => new[] { JsonWebTokenTypes.Private.DeviceCode }, + // For device codes, only the short "oi_dvc+jwt" form is valid. + TokenTypeHints.DeviceCode => new[] { JsonWebTokenTypes.Private.DeviceCode }, - // For refresh tokens, only the short "oi_reft+jwt" form is valid. - TokenTypeHints.RefreshToken => new[] { JsonWebTokenTypes.Private.RefreshToken }, + // For refresh tokens, only the short "oi_reft+jwt" form is valid. + TokenTypeHints.RefreshToken => new[] { JsonWebTokenTypes.Private.RefreshToken }, - // For user codes, only the short "oi_usrc+jwt" form is valid. - TokenTypeHints.UserCode => new[] { JsonWebTokenTypes.Private.UserCode }, + // For user codes, only the short "oi_usrc+jwt" form is valid. + TokenTypeHints.UserCode => new[] { JsonWebTokenTypes.Private.UserCode }, - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) - }) - }; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) + }) + }; - context.SecurityTokenHandler = context.Options.JsonWebTokenHandler; - context.TokenValidationParameters = parameters; + context.SecurityTokenHandler = context.Options.JsonWebTokenHandler; + context.TokenValidationParameters = parameters; - return default; - } + return default; } + } - /// - /// Contains the logic responsible of validating reference token identifiers. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateReferenceTokenIdentifier : IOpenIddictServerHandler - { - private readonly IOpenIddictTokenManager _tokenManager; - - public ValidateReferenceTokenIdentifier() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of validating reference token identifiers. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateReferenceTokenIdentifier : IOpenIddictServerHandler + { + private readonly IOpenIddictTokenManager _tokenManager; - public ValidateReferenceTokenIdentifier(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + public ValidateReferenceTokenIdentifier() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ResolveTokenValidationParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public ValidateReferenceTokenIdentifier(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; - public async ValueTask HandleAsync(ValidateTokenContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ResolveTokenValidationParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + public async ValueTask HandleAsync(ValidateTokenContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var token = context.Token.Length switch - { - // 12 may correspond to a normalized user code and 43 to any - // other base64url-encoded 256-bit reference token identifier. - 12 or 43 => await _tokenManager.FindByReferenceIdAsync(context.Token), + var token = context.Token.Length switch + { + // 12 may correspond to a normalized user code and 43 to any + // other base64url-encoded 256-bit reference token identifier. + 12 or 43 => await _tokenManager.FindByReferenceIdAsync(context.Token), - // A value higher than 12 (but lower than 50) may correspond to a user code - // containing dashes or any other non-digit character added by the end user. - // In this case, normalize the reference identifier before making the database lookup. - > 12 and < 50 => await _tokenManager.FindByReferenceIdAsync(NormalizeUserCode(context.Token)), + // A value higher than 12 (but lower than 50) may correspond to a user code + // containing dashes or any other non-digit character added by the end user. + // In this case, normalize the reference identifier before making the database lookup. + > 12 and < 50 => await _tokenManager.FindByReferenceIdAsync(NormalizeUserCode(context.Token)), - // If the token length differs, the token cannot be a reference token. - _ => null - }; + // If the token length differs, the token cannot be a reference token. + _ => null + }; - // If the reference token cannot be found, don't return an error to allow another handler to validate it. - if (token is null) - { - return; - } - - // If the type associated with the token entry doesn't match one of the expected types, return an error. - if (!(context.ValidTokenTypes.Count switch - { - 0 => true, // If no specific token type is expected, accept all token types at this stage. - 1 => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ElementAt(0)), - _ => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray()) - })) - { - context.Reject( - error: Errors.InvalidToken, - description: context.ValidTokenTypes.Count switch - { - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) - => SR.GetResourceString(SR.ID2001), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) - => SR.GetResourceString(SR.ID2002), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) - => SR.GetResourceString(SR.ID2003), + // If the reference token cannot be found, don't return an error to allow another handler to validate it. + if (token is null) + { + return; + } - _ => SR.GetResourceString(SR.ID2004) - }, - uri: context.ValidTokenTypes.Count switch - { - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) - => SR.FormatID8000(SR.ID2001), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) - => SR.FormatID8000(SR.ID2002), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) - => SR.FormatID8000(SR.ID2003), + // If the type associated with the token entry doesn't match one of the expected types, return an error. + if (!(context.ValidTokenTypes.Count switch + { + 0 => true, // If no specific token type is expected, accept all token types at this stage. + 1 => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ElementAt(0)), + _ => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray()) + })) + { + context.Reject( + error: Errors.InvalidToken, + description: context.ValidTokenTypes.Count switch + { + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) + => SR.GetResourceString(SR.ID2001), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) + => SR.GetResourceString(SR.ID2002), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) + => SR.GetResourceString(SR.ID2003), + + _ => SR.GetResourceString(SR.ID2004) + }, + uri: context.ValidTokenTypes.Count switch + { + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) + => SR.FormatID8000(SR.ID2001), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) + => SR.FormatID8000(SR.ID2002), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) + => SR.FormatID8000(SR.ID2003), - _ => SR.FormatID8000(SR.ID2004), - }); + _ => SR.FormatID8000(SR.ID2004), + }); - return; - } + return; + } - var payload = await _tokenManager.GetPayloadAsync(token); - if (string.IsNullOrEmpty(payload)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0026)); - } + var payload = await _tokenManager.GetPayloadAsync(token); + if (string.IsNullOrEmpty(payload)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0026)); + } - // Replace the token parameter by the payload resolved from the token entry - // and store the identifier of the reference token so it can be later - // used to restore the properties associated with the token. - context.Token = payload; - context.TokenId = await _tokenManager.GetIdAsync(token); - - // Note: unlike other tokens, user codes may be potentially entered manually by users in a web form. - // To make that easier, user codes are generally "beautified" by adding intermediate dashes to - // make them easier to read and type. Since these additional characters are not part of the original - // user codes, non-digit characters are filtered from the reference identifier using this local method. - static string NormalizeUserCode(string token) + // Replace the token parameter by the payload resolved from the token entry + // and store the identifier of the reference token so it can be later + // used to restore the properties associated with the token. + context.Token = payload; + context.TokenId = await _tokenManager.GetIdAsync(token); + + // Note: unlike other tokens, user codes may be potentially entered manually by users in a web form. + // To make that easier, user codes are generally "beautified" by adding intermediate dashes to + // make them easier to read and type. Since these additional characters are not part of the original + // user codes, non-digit characters are filtered from the reference identifier using this local method. + static string NormalizeUserCode(string token) + { + var builder = new StringBuilder(token); + for (var index = builder.Length - 1; index >= 0; index--) { - var builder = new StringBuilder(token); - for (var index = builder.Length - 1; index >= 0; index--) + var character = builder[index]; + if (character < '0' || character > '9') { - var character = builder[index]; - if (character < '0' || character > '9') - { - builder.Remove(index, 1); - } + builder.Remove(index, 1); } - - return builder.ToString(); } + + return builder.ToString(); } } + } + /// + /// Contains the logic responsible of validating tokens generated using IdentityModel. + /// + public class ValidateIdentityModelToken : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of validating tokens generated using IdentityModel. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateIdentityModelToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateReferenceTokenIdentifier.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateReferenceTokenIdentifier.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If a principal was already attached, don't overwrite it. - if (context.Principal is not null) - { - return default; - } + // If a principal was already attached, don't overwrite it. + if (context.Principal is not null) + { + return default; + } - // If the token cannot be read, don't return an error to allow another handler to validate it. - if (!context.SecurityTokenHandler.CanReadToken(context.Token)) - { - return default; - } + // If the token cannot be read, don't return an error to allow another handler to validate it. + if (!context.SecurityTokenHandler.CanReadToken(context.Token)) + { + return default; + } - // Special endpoints like introspection or revocation use a single parameter to convey - // multiple types of tokens (typically but not limited to access and refresh tokens). - // - // To speed up the token resolution process, the client can send a "token_type_hint" - // containing the type of the token: if the parameter doesn't match the actual type, - // the authorization server MUST use a fallback mechanism to determine whether the - // token can be introspected or revoked even if it's of a different type. - // - // This logic is not used by OpenIddict for IdentityModel tokens, as processing - // tokens of different type doesn't require re-parsing and re-validating them - // multiple times. As such, the "token_type_hint" parameter is only used in the - // Data Protection integration package and is ignored for IdentityModel tokens. - // - // For more information, see https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 - // and https://datatracker.ietf.org/doc/html/rfc7662#section-2.1. - - var result = context.SecurityTokenHandler.ValidateToken(context.Token, context.TokenValidationParameters); - if (!result.IsValid) - { - context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token); + // Special endpoints like introspection or revocation use a single parameter to convey + // multiple types of tokens (typically but not limited to access and refresh tokens). + // + // To speed up the token resolution process, the client can send a "token_type_hint" + // containing the type of the token: if the parameter doesn't match the actual type, + // the authorization server MUST use a fallback mechanism to determine whether the + // token can be introspected or revoked even if it's of a different type. + // + // This logic is not used by OpenIddict for IdentityModel tokens, as processing + // tokens of different type doesn't require re-parsing and re-validating them + // multiple times. As such, the "token_type_hint" parameter is only used in the + // Data Protection integration package and is ignored for IdentityModel tokens. + // + // For more information, see https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 + // and https://datatracker.ietf.org/doc/html/rfc7662#section-2.1. + + var result = context.SecurityTokenHandler.ValidateToken(context.Token, context.TokenValidationParameters); + if (!result.IsValid) + { + context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token); - context.Reject( - error: Errors.InvalidToken, - description: result.Exception switch + context.Reject( + error: Errors.InvalidToken, + description: result.Exception switch + { + SecurityTokenInvalidTypeException => context.ValidTokenTypes.Count switch { - SecurityTokenInvalidTypeException => context.ValidTokenTypes.Count switch - { - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) - => SR.GetResourceString(SR.ID2005), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) + => SR.GetResourceString(SR.ID2005), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) - => SR.GetResourceString(SR.ID2006), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) + => SR.GetResourceString(SR.ID2006), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) - => SR.GetResourceString(SR.ID2007), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) + => SR.GetResourceString(SR.ID2007), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) - => SR.GetResourceString(SR.ID2008), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) + => SR.GetResourceString(SR.ID2008), - _ => SR.GetResourceString(SR.ID2089) - }, + _ => SR.GetResourceString(SR.ID2089) + }, - SecurityTokenInvalidIssuerException => SR.GetResourceString(SR.ID2088), - SecurityTokenSignatureKeyNotFoundException => SR.GetResourceString(SR.ID2090), - SecurityTokenInvalidSignatureException => SR.GetResourceString(SR.ID2091), + SecurityTokenInvalidIssuerException => SR.GetResourceString(SR.ID2088), + SecurityTokenSignatureKeyNotFoundException => SR.GetResourceString(SR.ID2090), + SecurityTokenInvalidSignatureException => SR.GetResourceString(SR.ID2091), - _ => SR.GetResourceString(SR.ID2004) - }, - uri: result.Exception switch + _ => SR.GetResourceString(SR.ID2004) + }, + uri: result.Exception switch + { + SecurityTokenInvalidTypeException => context.ValidTokenTypes.Count switch { - SecurityTokenInvalidTypeException => context.ValidTokenTypes.Count switch - { - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) - => SR.FormatID8000(SR.ID2005), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) + => SR.FormatID8000(SR.ID2005), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) - => SR.FormatID8000(SR.ID2006), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) + => SR.FormatID8000(SR.ID2006), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) - => SR.FormatID8000(SR.ID2007), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) + => SR.FormatID8000(SR.ID2007), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) - => SR.FormatID8000(SR.ID2008), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) + => SR.FormatID8000(SR.ID2008), - _ => SR.FormatID8000(SR.ID2089) - }, + _ => SR.FormatID8000(SR.ID2089) + }, - SecurityTokenInvalidIssuerException => SR.FormatID8000(SR.ID2088), - SecurityTokenSignatureKeyNotFoundException => SR.FormatID8000(SR.ID2090), - SecurityTokenInvalidSignatureException => SR.FormatID8000(SR.ID2091), + SecurityTokenInvalidIssuerException => SR.FormatID8000(SR.ID2088), + SecurityTokenSignatureKeyNotFoundException => SR.FormatID8000(SR.ID2090), + SecurityTokenInvalidSignatureException => SR.FormatID8000(SR.ID2091), - _ => SR.FormatID8000(SR.ID2004) - }); + _ => SR.FormatID8000(SR.ID2004) + }); - return default; - } + return default; + } - // Get the JWT token. If the token is encrypted using JWE, retrieve the inner token. - var token = (JsonWebToken) result.SecurityToken; - if (token.InnerToken is not null) - { - token = token.InnerToken; - } + // Get the JWT token. If the token is encrypted using JWE, retrieve the inner token. + var token = (JsonWebToken) result.SecurityToken; + if (token.InnerToken is not null) + { + token = token.InnerToken; + } - // Attach the principal extracted from the token to the parent event context and store - // the token type (resolved from "typ" or "token_usage") as a special private claim. - context.Principal = new ClaimsPrincipal(result.ClaimsIdentity).SetTokenType(result.TokenType switch - { - null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), + // Attach the principal extracted from the token to the parent event context and store + // the token type (resolved from "typ" or "token_usage") as a special private claim. + context.Principal = new ClaimsPrincipal(result.ClaimsIdentity).SetTokenType(result.TokenType switch + { + null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), - // Both at+jwt and application/at+jwt are supported for access tokens. - JsonWebTokenTypes.AccessToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken - => TokenTypeHints.AccessToken, + // Both at+jwt and application/at+jwt are supported for access tokens. + JsonWebTokenTypes.AccessToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken + => TokenTypeHints.AccessToken, - // Both JWT and application/JWT are supported for identity tokens. - JsonWebTokenTypes.IdentityToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.IdentityToken - => TokenTypeHints.IdToken, + // Both JWT and application/JWT are supported for identity tokens. + JsonWebTokenTypes.IdentityToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.IdentityToken + => TokenTypeHints.IdToken, - JsonWebTokenTypes.Private.AuthorizationCode => TokenTypeHints.AuthorizationCode, - JsonWebTokenTypes.Private.DeviceCode => TokenTypeHints.DeviceCode, - JsonWebTokenTypes.Private.RefreshToken => TokenTypeHints.RefreshToken, - JsonWebTokenTypes.Private.UserCode => TokenTypeHints.UserCode, + JsonWebTokenTypes.Private.AuthorizationCode => TokenTypeHints.AuthorizationCode, + JsonWebTokenTypes.Private.DeviceCode => TokenTypeHints.DeviceCode, + JsonWebTokenTypes.Private.RefreshToken => TokenTypeHints.RefreshToken, + JsonWebTokenTypes.Private.UserCode => TokenTypeHints.UserCode, - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) - }); + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) + }); - // Restore the claim destinations from the special oi_cl_dstn claim (represented as a dictionary/JSON object). - if (token.TryGetPayloadValue(Claims.Private.ClaimDestinationsMap, out ImmutableDictionary destinations)) - { - context.Principal.SetDestinations(destinations); - } + // Restore the claim destinations from the special oi_cl_dstn claim (represented as a dictionary/JSON object). + if (token.TryGetPayloadValue(Claims.Private.ClaimDestinationsMap, out ImmutableDictionary destinations)) + { + context.Principal.SetDestinations(destinations); + } - context.Logger.LogTrace(SR.GetResourceString(SR.ID6001), context.Token, context.Principal.Claims); + context.Logger.LogTrace(SR.GetResourceString(SR.ID6001), context.Token, context.Principal.Claims); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of normalizing the scope claims stored in the tokens. + /// + public class NormalizeScopeClaims : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of normalizing the scope claims stored in the tokens. + /// Gets the default descriptor definition assigned to this handler. /// - public class NormalizeScopeClaims : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateIdentityModelToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateIdentityModelToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.Principal is null) - { - return default; - } - - // Note: in previous OpenIddict versions, scopes were represented as a JSON array - // and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim - // is formatted as a unique space-separated string containing all the granted scopes. - // To ensure access tokens generated by previous versions are still correctly handled, - // both formats (unique space-separated string or multiple scope claims) must be supported. - // To achieve that, all the "scope" claims are combined into a single one containg all the values. - // Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. - var scopes = context.Principal.GetClaims(Claims.Scope); - if (scopes.Length > 1) - { - context.Principal.SetClaim(Claims.Scope, string.Join(" ", scopes)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Principal is null) + { return default; } + + // Note: in previous OpenIddict versions, scopes were represented as a JSON array + // and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim + // is formatted as a unique space-separated string containing all the granted scopes. + // To ensure access tokens generated by previous versions are still correctly handled, + // both formats (unique space-separated string or multiple scope claims) must be supported. + // To achieve that, all the "scope" claims are combined into a single one containg all the values. + // Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. + var scopes = context.Principal.GetClaims(Claims.Scope); + if (scopes.Length > 1) + { + context.Principal.SetClaim(Claims.Scope, string.Join(" ", scopes)); + } + + return default; } + } + /// + /// Contains the logic responsible of mapping internal claims used by OpenIddict. + /// + public class MapInternalClaims : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of mapping internal claims used by OpenIddict. + /// Gets the default descriptor definition assigned to this handler. /// - public class MapInternalClaims : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(NormalizeScopeClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(NormalizeScopeClaims.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.Principal is null) - { - return default; - } + if (context.Principal is null) + { + return default; + } - // To reduce the size of tokens, some of the private claims used by OpenIddict - // are mapped to their standard equivalent before being removed from the token. - // This handler is responsible of adding back the private claims to the principal - // when receiving the token (e.g "oi_prst" is resolved from the "scope" claim). + // To reduce the size of tokens, some of the private claims used by OpenIddict + // are mapped to their standard equivalent before being removed from the token. + // This handler is responsible of adding back the private claims to the principal + // when receiving the token (e.g "oi_prst" is resolved from the "scope" claim). - // In OpenIddict 3.0, the creation date of a token is stored in "oi_crt_dt". - // If the claim doesn't exist, try to infer it from the standard "iat" JWT claim. - if (!context.Principal.HasClaim(Claims.Private.CreationDate)) + // In OpenIddict 3.0, the creation date of a token is stored in "oi_crt_dt". + // If the claim doesn't exist, try to infer it from the standard "iat" JWT claim. + if (!context.Principal.HasClaim(Claims.Private.CreationDate)) + { + var date = context.Principal.GetClaim(Claims.IssuedAt); + if (!string.IsNullOrEmpty(date) && + long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { - var date = context.Principal.GetClaim(Claims.IssuedAt); - if (!string.IsNullOrEmpty(date) && - long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) - { - context.Principal.SetCreationDate(DateTimeOffset.FromUnixTimeSeconds(value)); - } + context.Principal.SetCreationDate(DateTimeOffset.FromUnixTimeSeconds(value)); } + } - // In OpenIddict 3.0, the expiration date of a token is stored in "oi_exp_dt". - // If the claim doesn't exist, try to infer it from the standard "exp" JWT claim. - if (!context.Principal.HasClaim(Claims.Private.ExpirationDate)) + // In OpenIddict 3.0, the expiration date of a token is stored in "oi_exp_dt". + // If the claim doesn't exist, try to infer it from the standard "exp" JWT claim. + if (!context.Principal.HasClaim(Claims.Private.ExpirationDate)) + { + var date = context.Principal.GetClaim(Claims.ExpiresAt); + if (!string.IsNullOrEmpty(date) && + long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { - var date = context.Principal.GetClaim(Claims.ExpiresAt); - if (!string.IsNullOrEmpty(date) && - long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) - { - context.Principal.SetExpirationDate(DateTimeOffset.FromUnixTimeSeconds(value)); - } + context.Principal.SetExpirationDate(DateTimeOffset.FromUnixTimeSeconds(value)); } + } - // In OpenIddict 3.0, the audiences allowed to receive a token are stored in "oi_aud". - // If no such claim exists, try to infer them from the standard "aud" JWT claims. - if (!context.Principal.HasClaim(Claims.Private.Audience)) + // In OpenIddict 3.0, the audiences allowed to receive a token are stored in "oi_aud". + // If no such claim exists, try to infer them from the standard "aud" JWT claims. + if (!context.Principal.HasClaim(Claims.Private.Audience)) + { + var audiences = context.Principal.GetClaims(Claims.Audience); + if (audiences.Any()) { - var audiences = context.Principal.GetClaims(Claims.Audience); - if (audiences.Any()) - { - context.Principal.SetAudiences(audiences); - } + context.Principal.SetAudiences(audiences); } + } - // In OpenIddict 3.0, the presenters allowed to use a token are stored in "oi_prst". - // If no such claim exists, try to infer them from the standard "azp" and "client_id" JWT claims. - // - // Note: in previous OpenIddict versions, the presenters were represented in JWT tokens - // using the "azp" claim (defined by OpenID Connect), for which a single value could be - // specified. To ensure presenters stored in JWT tokens created by OpenIddict 1.x/2.x - // can still be read with OpenIddict 3.0, the presenter is automatically inferred from - // the "azp" or "client_id" claim if no "oi_prst" claim was found in the principal. - if (!context.Principal.HasClaim(Claims.Private.Presenter)) - { - var presenter = context.Principal.GetClaim(Claims.AuthorizedParty) ?? - context.Principal.GetClaim(Claims.ClientId); + // In OpenIddict 3.0, the presenters allowed to use a token are stored in "oi_prst". + // If no such claim exists, try to infer them from the standard "azp" and "client_id" JWT claims. + // + // Note: in previous OpenIddict versions, the presenters were represented in JWT tokens + // using the "azp" claim (defined by OpenID Connect), for which a single value could be + // specified. To ensure presenters stored in JWT tokens created by OpenIddict 1.x/2.x + // can still be read with OpenIddict 3.0, the presenter is automatically inferred from + // the "azp" or "client_id" claim if no "oi_prst" claim was found in the principal. + if (!context.Principal.HasClaim(Claims.Private.Presenter)) + { + var presenter = context.Principal.GetClaim(Claims.AuthorizedParty) ?? + context.Principal.GetClaim(Claims.ClientId); - if (!string.IsNullOrEmpty(presenter)) - { - context.Principal.SetPresenters(presenter); - } + if (!string.IsNullOrEmpty(presenter)) + { + context.Principal.SetPresenters(presenter); } + } - // In OpenIddict 3.0, the scopes granted to an application are stored in "oi_scp". - // If no such claim exists, try to infer them from the standard "scope" JWT claim, - // which is guaranteed to be a unique space-separated claim containing all the values. - if (!context.Principal.HasClaim(Claims.Private.Scope)) + // In OpenIddict 3.0, the scopes granted to an application are stored in "oi_scp". + // If no such claim exists, try to infer them from the standard "scope" JWT claim, + // which is guaranteed to be a unique space-separated claim containing all the values. + if (!context.Principal.HasClaim(Claims.Private.Scope)) + { + var scope = context.Principal.GetClaim(Claims.Scope); + if (!string.IsNullOrEmpty(scope)) { - var scope = context.Principal.GetClaim(Claims.Scope); - if (!string.IsNullOrEmpty(scope)) - { - context.Principal.SetScopes(scope.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)); - } + context.Principal.SetScopes(scope.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)); } - - return default; } - } - /// - /// Contains the logic responsible of restoring the properties associated with a reference token entry. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class RestoreReferenceTokenProperties : IOpenIddictServerHandler - { - private readonly IOpenIddictTokenManager _tokenManager; + return default; + } + } - public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of restoring the properties associated with a reference token entry. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class RestoreReferenceTokenProperties : IOpenIddictServerHandler + { + private readonly IOpenIddictTokenManager _tokenManager; - public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(MapInternalClaims.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; - public async ValueTask HandleAsync(ValidateTokenContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(MapInternalClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + public async ValueTask HandleAsync(ValidateTokenContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.Principal is null || string.IsNullOrEmpty(context.TokenId)) - { - return; - } + throw new ArgumentNullException(nameof(context)); + } - var token = await _tokenManager.FindByIdAsync(context.TokenId); - if (token is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); - } + if (context.Principal is null || string.IsNullOrEmpty(context.TokenId)) + { + return; + } - // Restore the creation/expiration dates/identifiers from the token entry metadata. - context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) - .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) - .SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) - .SetTokenId(await _tokenManager.GetIdAsync(token)) - .SetTokenType(await _tokenManager.GetTypeAsync(token)); + var token = await _tokenManager.FindByIdAsync(context.TokenId); + if (token is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); } + + // Restore the creation/expiration dates/identifiers from the token entry metadata. + context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) + .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) + .SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) + .SetTokenId(await _tokenManager.GetIdAsync(token)) + .SetTokenType(await _tokenManager.GetTypeAsync(token)); } + } + /// + /// Contains the logic responsible of rejecting authentication demands for which no valid principal was resolved. + /// + public class ValidatePrincipal : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authentication demands for which no valid principal was resolved. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidatePrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.Principal is null) - { - context.Reject( - error: Errors.InvalidToken, - description: context.ValidTokenTypes.Count switch - { - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) - => SR.GetResourceString(SR.ID2001), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) - => SR.GetResourceString(SR.ID2002), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) - => SR.GetResourceString(SR.ID2003), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.IdToken) - => SR.GetResourceString(SR.ID2009), - - _ => SR.GetResourceString(SR.ID2004) - }, - uri: context.ValidTokenTypes.Count switch - { - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) - => SR.FormatID8000(SR.ID2001), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) - => SR.FormatID8000(SR.ID2002), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) - => SR.FormatID8000(SR.ID2003), - 1 when context.ValidTokenTypes.Contains(TokenTypeHints.IdToken) - => SR.FormatID8000(SR.ID2009), + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - _ => SR.FormatID8000(SR.ID2004) - }); + if (context.Principal is null) + { + context.Reject( + error: Errors.InvalidToken, + description: context.ValidTokenTypes.Count switch + { + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) + => SR.GetResourceString(SR.ID2001), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) + => SR.GetResourceString(SR.ID2002), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) + => SR.GetResourceString(SR.ID2003), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.IdToken) + => SR.GetResourceString(SR.ID2009), + + _ => SR.GetResourceString(SR.ID2004) + }, + uri: context.ValidTokenTypes.Count switch + { + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) + => SR.FormatID8000(SR.ID2001), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) + => SR.FormatID8000(SR.ID2002), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) + => SR.FormatID8000(SR.ID2003), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.IdToken) + => SR.FormatID8000(SR.ID2009), + _ => SR.FormatID8000(SR.ID2004) + }); - return default; - } - // When using JWT or Data Protection tokens, the correct token type is always enforced by IdentityModel - // (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings). - // To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used, - // which requires that a special claim containing the token type be present in the security principal. - var type = context.Principal.GetTokenType(); - if (string.IsNullOrEmpty(type)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0004)); - } + return default; + } - if (context.ValidTokenTypes.Count > 0 && !context.ValidTokenTypes.Contains(type)) - { - throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes))); - } + // When using JWT or Data Protection tokens, the correct token type is always enforced by IdentityModel + // (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings). + // To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used, + // which requires that a special claim containing the token type be present in the security principal. + var type = context.Principal.GetTokenType(); + if (string.IsNullOrEmpty(type)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0004)); + } - return default; + if (context.ValidTokenTypes.Count > 0 && !context.ValidTokenTypes.Contains(type)) + { + throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes))); } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authentication demands that use an expired token. + /// + public class ValidateExpirationDate : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting authentication demands that use an expired token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateExpirationDate : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidatePrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidatePrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.DisableLifetimeValidation) - { - return default; - } + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var date = context.Principal.GetExpirationDate(); - if (date.HasValue && date.Value < DateTimeOffset.UtcNow) - { - context.Reject( - error: context.Principal.GetTokenType() switch - { - TokenTypeHints.DeviceCode => Errors.ExpiredToken, - _ => Errors.InvalidToken - }, - description: context.Principal.GetTokenType() switch - { - TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2016), - TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2017), - TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2018), + if (context.DisableLifetimeValidation) + { + return default; + } - _ => SR.GetResourceString(SR.ID2019) - }, - uri: context.Principal.GetTokenType() switch - { - TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2016), - TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2017), - TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2018), + var date = context.Principal.GetExpirationDate(); + if (date.HasValue && date.Value < DateTimeOffset.UtcNow) + { + context.Reject( + error: context.Principal.GetTokenType() switch + { + TokenTypeHints.DeviceCode => Errors.ExpiredToken, + _ => Errors.InvalidToken + }, + description: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2016), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2017), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2018), - _ => SR.FormatID8000(SR.ID2019) - }); + _ => SR.GetResourceString(SR.ID2019) + }, + uri: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2016), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2017), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2018), - return default; - } + _ => SR.FormatID8000(SR.ID2019) + }); return default; } + + return default; } + } + + /// + /// Contains the logic responsible of rejecting authentication demands that + /// use a token whose entry is no longer valid (e.g was revoked). + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateTokenEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictTokenManager _tokenManager; + + public ValidateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateTokenEntry(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// - /// Contains the logic responsible of rejecting authentication demands that - /// use a token whose entry is no longer valid (e.g was revoked). - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTokenEntry : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + public async ValueTask HandleAsync(ValidateTokenContext context) { - private readonly IOpenIddictTokenManager _tokenManager; - - public ValidateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateTokenEntry(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + // Extract the token identifier from the authentication principal. + // If no token identifier can be found, this indicates that the token + // has no backing database entry (e.g an access token or an identity token). + var identifier = context.Principal.GetTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + return; + } - public async ValueTask HandleAsync(ValidateTokenContext context) + // If the token entry cannot be found, return a generic error. + var token = await _tokenManager.FindByIdAsync(identifier); + if (token is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + context.Reject( + error: Errors.InvalidToken, + description: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2001), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2002), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2003), - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + _ => SR.GetResourceString(SR.ID2004) + }, + uri: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2001), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2002), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2003), - // Extract the token identifier from the authentication principal. - // If no token identifier can be found, this indicates that the token - // has no backing database entry (e.g an access token or an identity token). - var identifier = context.Principal.GetTokenId(); - if (string.IsNullOrEmpty(identifier)) - { - return; - } + _ => SR.FormatID8000(SR.ID2004) + }); + + return; + } - // If the token entry cannot be found, return a generic error. - var token = await _tokenManager.FindByIdAsync(identifier); - if (token is null) + // If the token is already marked as redeemed, this may indicate that it was compromised. + // In this case, revoke the entire chain of tokens associated with the authorization. + // Special logic is used to avoid revoking refresh tokens already marked as redeemed to allow for a small leeway. + // Note: the authorization itself is not revoked to allow the legitimate client to start a new flow. + // See https://tools.ietf.org/html/rfc6749#section-10.5 for more information. + if (await _tokenManager.HasStatusAsync(token, Statuses.Redeemed)) + { + if (!context.Principal.HasTokenType(TokenTypeHints.RefreshToken) || !await IsReusableAsync(token)) { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), identifier); + context.Reject( error: Errors.InvalidToken, description: context.Principal.GetTokenType() switch { - TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2001), - TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2002), - TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2003), + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2010), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2011), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2012), - _ => SR.GetResourceString(SR.ID2004) + _ => SR.GetResourceString(SR.ID2013) }, uri: context.Principal.GetTokenType() switch { - TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2001), - TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2002), - TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2003), + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2010), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2011), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2012), - _ => SR.FormatID8000(SR.ID2004) + _ => SR.FormatID8000(SR.ID2013) }); + // Revoke all the token entries associated with the authorization. + await TryRevokeChainAsync(await _tokenManager.GetAuthorizationIdAsync(token)); + return; } - // If the token is already marked as redeemed, this may indicate that it was compromised. - // In this case, revoke the entire chain of tokens associated with the authorization. - // Special logic is used to avoid revoking refresh tokens already marked as redeemed to allow for a small leeway. - // Note: the authorization itself is not revoked to allow the legitimate client to start a new flow. - // See https://tools.ietf.org/html/rfc6749#section-10.5 for more information. - if (await _tokenManager.HasStatusAsync(token, Statuses.Redeemed)) - { - if (!context.Principal.HasTokenType(TokenTypeHints.RefreshToken) || !await IsReusableAsync(token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), identifier); - - context.Reject( - error: Errors.InvalidToken, - description: context.Principal.GetTokenType() switch - { - TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2010), - TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2011), - TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2012), - - _ => SR.GetResourceString(SR.ID2013) - }, - uri: context.Principal.GetTokenType() switch - { - TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2010), - TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2011), - TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2012), - - _ => SR.FormatID8000(SR.ID2013) - }); - - // Revoke all the token entries associated with the authorization. - await TryRevokeChainAsync(await _tokenManager.GetAuthorizationIdAsync(token)); - - return; - } + return; + } - return; - } + // If the token is not marked as valid yet, return an authorization_pending error. + if (await _tokenManager.HasStatusAsync(token, Statuses.Inactive)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6003), identifier); - // If the token is not marked as valid yet, return an authorization_pending error. - if (await _tokenManager.HasStatusAsync(token, Statuses.Inactive)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6003), identifier); + context.Reject( + error: Errors.AuthorizationPending, + description: SR.GetResourceString(SR.ID2014), + uri: SR.FormatID8000(SR.ID2014)); - context.Reject( - error: Errors.AuthorizationPending, - description: SR.GetResourceString(SR.ID2014), - uri: SR.FormatID8000(SR.ID2014)); + return; + } - return; - } + // If the token is marked as rejected, return an access_denied error. + if (await _tokenManager.HasStatusAsync(token, Statuses.Rejected)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6004), identifier); - // If the token is marked as rejected, return an access_denied error. - if (await _tokenManager.HasStatusAsync(token, Statuses.Rejected)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6004), identifier); + context.Reject( + error: Errors.AccessDenied, + description: SR.GetResourceString(SR.ID2015), + uri: SR.FormatID8000(SR.ID2015)); - context.Reject( - error: Errors.AccessDenied, - description: SR.GetResourceString(SR.ID2015), - uri: SR.FormatID8000(SR.ID2015)); + return; + } - return; - } + if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); - if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); + context.Reject( + error: Errors.InvalidToken, + description: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2016), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2017), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2018), - context.Reject( - error: Errors.InvalidToken, - description: context.Principal.GetTokenType() switch - { - TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2016), - TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2017), - TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2018), + _ => SR.GetResourceString(SR.ID2019) + }, + uri: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2016), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2017), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2018), - _ => SR.GetResourceString(SR.ID2019) - }, - uri: context.Principal.GetTokenType() switch - { - TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2016), - TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2017), - TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2018), + _ => SR.FormatID8000(SR.ID2019) + }); - _ => SR.FormatID8000(SR.ID2019) - }); + return; + } - return; - } + // Restore the creation/expiration dates/identifiers from the token entry metadata. + context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) + .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) + .SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) + .SetTokenId(await _tokenManager.GetIdAsync(token)) + .SetTokenType(await _tokenManager.GetTypeAsync(token)); - // Restore the creation/expiration dates/identifiers from the token entry metadata. - context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) - .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) - .SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) - .SetTokenId(await _tokenManager.GetIdAsync(token)) - .SetTokenType(await _tokenManager.GetTypeAsync(token)); + async ValueTask IsReusableAsync(object token) + { + // If the reuse leeway was set to null, return false to indicate + // that the refresh token is already redeemed and cannot be reused. + if (context.Options.RefreshTokenReuseLeeway is null) + { + return false; + } - async ValueTask IsReusableAsync(object token) + var date = await _tokenManager.GetRedemptionDateAsync(token); + if (date is null || DateTimeOffset.UtcNow < date + context.Options.RefreshTokenReuseLeeway) { - // If the reuse leeway was set to null, return false to indicate - // that the refresh token is already redeemed and cannot be reused. - if (context.Options.RefreshTokenReuseLeeway is null) - { - return false; - } + return true; + } - var date = await _tokenManager.GetRedemptionDateAsync(token); - if (date is null || DateTimeOffset.UtcNow < date + context.Options.RefreshTokenReuseLeeway) - { - return true; - } + return false; + } - return false; + async ValueTask TryRevokeChainAsync(string? identifier) + { + if (string.IsNullOrEmpty(identifier)) + { + return; } - async ValueTask TryRevokeChainAsync(string? identifier) + // Revoke all the token entries associated with the authorization, + // including the redeemed token that was used in the token request. + await foreach (var token in _tokenManager.FindByAuthorizationIdAsync(identifier)) { - if (string.IsNullOrEmpty(identifier)) - { - return; - } - - // Revoke all the token entries associated with the authorization, - // including the redeemed token that was used in the token request. - await foreach (var token in _tokenManager.FindByAuthorizationIdAsync(identifier)) - { - await _tokenManager.TryRevokeAsync(token); - } + await _tokenManager.TryRevokeAsync(token); } } } + } - /// - /// Contains the logic responsible of authentication demands a token whose - /// associated authorization entry is no longer valid (e.g was revoked). - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateAuthorizationEntry : IOpenIddictServerHandler - { - private readonly IOpenIddictAuthorizationManager _authorizationManager; - - public ValidateAuthorizationEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of authentication demands a token whose + /// associated authorization entry is no longer valid (e.g was revoked). + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateAuthorizationEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictAuthorizationManager _authorizationManager; - public ValidateAuthorizationEntry(IOpenIddictAuthorizationManager authorizationManager) - => _authorizationManager = authorizationManager; + public ValidateAuthorizationEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public ValidateAuthorizationEntry(IOpenIddictAuthorizationManager authorizationManager) + => _authorizationManager = authorizationManager; - public async ValueTask HandleAsync(ValidateTokenContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + public async ValueTask HandleAsync(ValidateTokenContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var identifier = context.Principal.GetAuthorizationId(); - if (string.IsNullOrEmpty(identifier)) - { - return; - } + var identifier = context.Principal.GetAuthorizationId(); + if (string.IsNullOrEmpty(identifier)) + { + return; + } - var authorization = await _authorizationManager.FindByIdAsync(identifier); - if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier); + var authorization = await _authorizationManager.FindByIdAsync(identifier); + if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier); - context.Reject( - error: Errors.InvalidToken, - description: context.Principal.GetTokenType() switch - { - TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2020), - TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2021), - TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2022), + context.Reject( + error: Errors.InvalidToken, + description: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2020), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2021), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2022), - _ => SR.GetResourceString(SR.ID2023) - }, - uri: context.Principal.GetTokenType() switch - { - TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2020), - TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2021), - TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2022), + _ => SR.GetResourceString(SR.ID2023) + }, + uri: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2020), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2021), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2022), - _ => SR.FormatID8000(SR.ID2023) - }); + _ => SR.FormatID8000(SR.ID2023) + }); - return; - } + return; } } + } + /// + /// Contains the logic responsible of resolving the signing and encryption credentials used to protect tokens. + /// + public class AttachSecurityCredentials : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of resolving the signing and encryption credentials used to protect tokens. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachSecurityCredentials : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(GenerateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(GenerateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.SecurityTokenHandler = context.Options.JsonWebTokenHandler; + context.SecurityTokenHandler = context.Options.JsonWebTokenHandler; - context.EncryptionCredentials = context.TokenType switch - { - // Note: unlike other tokens, encryption can be disabled for access tokens. - TokenTypeHints.AccessToken when context.Options.DisableAccessTokenEncryption => null, - TokenTypeHints.IdToken => null, + context.EncryptionCredentials = context.TokenType switch + { + // Note: unlike other tokens, encryption can be disabled for access tokens. + TokenTypeHints.AccessToken when context.Options.DisableAccessTokenEncryption => null, + TokenTypeHints.IdToken => null, - _ => context.Options.EncryptionCredentials.First() - }; + _ => context.Options.EncryptionCredentials.First() + }; - context.SigningCredentials = context.TokenType switch - { - // Note: unlike other tokens, identity tokens can only be signed using an asymmetric key - // as they are meant to be validated by clients using the public keys exposed by the server. - TokenTypeHints.IdToken => context.Options.SigningCredentials.First(credentials => - credentials.Key is AsymmetricSecurityKey), + context.SigningCredentials = context.TokenType switch + { + // Note: unlike other tokens, identity tokens can only be signed using an asymmetric key + // as they are meant to be validated by clients using the public keys exposed by the server. + TokenTypeHints.IdToken => context.Options.SigningCredentials.First(credentials => + credentials.Key is AsymmetricSecurityKey), - _ => context.Options.SigningCredentials.First() - }; + _ => context.Options.SigningCredentials.First() + }; - return default; - } + return default; + } + } + + /// + /// Contains the logic responsible of creating a token entry. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class CreateTokenEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictTokenManager _tokenManager; + + public CreateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public CreateTokenEntry( + IOpenIddictApplicationManager applicationManager, + IOpenIddictTokenManager tokenManager) + { + _applicationManager = applicationManager; + _tokenManager = tokenManager; } /// - /// Contains the logic responsible of creating a token entry. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class CreateTokenEntry : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(AttachSecurityCredentials.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(GenerateTokenContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - private readonly IOpenIddictTokenManager _tokenManager; - - public CreateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public CreateTokenEntry( - IOpenIddictApplicationManager applicationManager, - IOpenIddictTokenManager tokenManager) + if (context is null) { - _applicationManager = applicationManager; - _tokenManager = tokenManager; + throw new ArgumentNullException(nameof(context)); } - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(AttachSecurityCredentials.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(GenerateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var descriptor = new OpenIddictTokenDescriptor - { - AuthorizationId = context.Principal.GetAuthorizationId(), - CreationDate = context.Principal.GetCreationDate(), - ExpirationDate = context.Principal.GetExpirationDate(), - Principal = context.Principal, - Type = context.TokenType - }; - - descriptor.Status = context.TokenType switch - { - // When initially created, device codes are marked as inactive. When the user - // approves the authorization demand, the UpdateReferenceDeviceCodeEntry handler - // changes the status to "active" and attaches a new payload with the claims - // corresponding the user, which allows the client to redeem the device code. - TokenTypeHints.DeviceCode => Statuses.Inactive, + var descriptor = new OpenIddictTokenDescriptor + { + AuthorizationId = context.Principal.GetAuthorizationId(), + CreationDate = context.Principal.GetCreationDate(), + ExpirationDate = context.Principal.GetExpirationDate(), + Principal = context.Principal, + Type = context.TokenType + }; + + descriptor.Status = context.TokenType switch + { + // When initially created, device codes are marked as inactive. When the user + // approves the authorization demand, the UpdateReferenceDeviceCodeEntry handler + // changes the status to "active" and attaches a new payload with the claims + // corresponding the user, which allows the client to redeem the device code. + TokenTypeHints.DeviceCode => Statuses.Inactive, - // For all other tokens, "valid" is the default status. - _ => Statuses.Valid - }; + // For all other tokens, "valid" is the default status. + _ => Statuses.Valid + }; - descriptor.Subject = context.TokenType switch - { - // Device and user codes are not bound to a user, until authorization is granted. - TokenTypeHints.DeviceCode or TokenTypeHints.UserCode => null, + descriptor.Subject = context.TokenType switch + { + // Device and user codes are not bound to a user, until authorization is granted. + TokenTypeHints.DeviceCode or TokenTypeHints.UserCode => null, - // For all other tokens, the subject is resolved from the principal. - _ => context.Principal.GetClaim(Claims.Subject) - }; + // For all other tokens, the subject is resolved from the principal. + _ => context.Principal.GetClaim(Claims.Subject) + }; - // If the client application is known, associate it with the token. - if (!string.IsNullOrEmpty(context.ClientId)) + // If the client application is known, associate it with the token. + if (!string.IsNullOrEmpty(context.ClientId)) + { + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) { - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0017)); - } - - descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0017)); } - var token = await _tokenManager.CreateAsync(descriptor); - if (token is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0019)); - } + descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); + } - var identifier = await _tokenManager.GetIdAsync(token); + var token = await _tokenManager.CreateAsync(descriptor); + if (token is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0019)); + } - // Attach the token identifier to the principal so that it can be stored in the token. - context.Principal.SetTokenId(identifier); + var identifier = await _tokenManager.GetIdAsync(token); - context.Logger.LogTrace(SR.GetResourceString(SR.ID6012), context.TokenType, identifier); - } + // Attach the token identifier to the principal so that it can be stored in the token. + context.Principal.SetTokenId(identifier); + + context.Logger.LogTrace(SR.GetResourceString(SR.ID6012), context.TokenType, identifier); } + } + /// + /// Contains the logic responsible of generating a token using IdentityModel. + /// + public class GenerateIdentityModelToken : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of generating a token using IdentityModel. + /// Gets the default descriptor definition assigned to this handler. /// - public class GenerateIdentityModelToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(CreateTokenEntry.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(GenerateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(CreateTokenEntry.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(GenerateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If a token was already attached by another handler, don't overwrite it. - if (!string.IsNullOrEmpty(context.Token)) - { - return default; - } + // If a token was already attached by another handler, don't overwrite it. + if (!string.IsNullOrEmpty(context.Token)) + { + return default; + } - if (context.Principal is null or { Identity: not ClaimsIdentity }) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0022)); - } + if (context.Principal is null or { Identity: not ClaimsIdentity }) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0022)); + } - // Clone the principal and exclude the private claims mapped to standard JWT claims. - var principal = context.Principal.Clone(claim => claim.Type switch - { - Claims.Private.CreationDate or Claims.Private.ExpirationDate or Claims.Private.TokenType => false, + // Clone the principal and exclude the private claims mapped to standard JWT claims. + var principal = context.Principal.Clone(claim => claim.Type switch + { + Claims.Private.CreationDate or Claims.Private.ExpirationDate or Claims.Private.TokenType => false, - Claims.Private.Audience - when context.TokenType is TokenTypeHints.AccessToken or TokenTypeHints.IdToken => false, + Claims.Private.Audience + when context.TokenType is TokenTypeHints.AccessToken or TokenTypeHints.IdToken => false, - Claims.Private.Scope when context.TokenType is TokenTypeHints.AccessToken => false, + Claims.Private.Scope when context.TokenType is TokenTypeHints.AccessToken => false, - _ => true - }); + _ => true + }); - if (principal is null or { Identity: not ClaimsIdentity }) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0020)); - } + if (principal is null or { Identity: not ClaimsIdentity }) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0020)); + } - var claims = new Dictionary(StringComparer.Ordinal); + var claims = new Dictionary(StringComparer.Ordinal); - // For access and identity tokens, set the public audience claims - // using the private audience claims from the security principal. - if (context.TokenType is TokenTypeHints.AccessToken or TokenTypeHints.IdToken) + // For access and identity tokens, set the public audience claims + // using the private audience claims from the security principal. + if (context.TokenType is TokenTypeHints.AccessToken or TokenTypeHints.IdToken) + { + var audiences = context.Principal.GetAudiences(); + if (audiences.Any()) { - var audiences = context.Principal.GetAudiences(); - if (audiences.Any()) + claims.Add(Claims.Audience, audiences.Length switch { - claims.Add(Claims.Audience, audiences.Length switch - { - 1 => audiences.ElementAt(0), - _ => audiences - }); - } + 1 => audiences.ElementAt(0), + _ => audiences + }); } + } - // For access tokens, set the public scope claim using the private scope claims from the principal. - // Note: scopes are deliberately formatted as a single space-separated - // string to respect the usual representation of the standard scope claim. - // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04. - if (context.TokenType is TokenTypeHints.AccessToken) + // For access tokens, set the public scope claim using the private scope claims from the principal. + // Note: scopes are deliberately formatted as a single space-separated + // string to respect the usual representation of the standard scope claim. + // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04. + if (context.TokenType is TokenTypeHints.AccessToken) + { + var scopes = context.Principal.GetScopes(); + if (scopes.Any()) { - var scopes = context.Principal.GetScopes(); - if (scopes.Any()) - { - claims.Add(Claims.Scope, string.Join(" ", scopes)); - } + claims.Add(Claims.Scope, string.Join(" ", scopes)); } + } - // For authorization/device/user codes and refresh tokens, - // attach claims destinations to the JWT claims collection. - if (context.TokenType is TokenTypeHints.AuthorizationCode or TokenTypeHints.DeviceCode or - TokenTypeHints.RefreshToken or TokenTypeHints.UserCode) + // For authorization/device/user codes and refresh tokens, + // attach claims destinations to the JWT claims collection. + if (context.TokenType is TokenTypeHints.AuthorizationCode or TokenTypeHints.DeviceCode or + TokenTypeHints.RefreshToken or TokenTypeHints.UserCode) + { + var destinations = principal.GetDestinations(); + if (destinations.Count != 0) { - var destinations = principal.GetDestinations(); - if (destinations.Count != 0) - { - claims.Add(Claims.Private.ClaimDestinationsMap, destinations); - } + claims.Add(Claims.Private.ClaimDestinationsMap, destinations); } + } - var descriptor = new SecurityTokenDescriptor + var descriptor = new SecurityTokenDescriptor + { + Claims = claims, + EncryptingCredentials = context.EncryptionCredentials, + Expires = context.Principal.GetExpirationDate()?.UtcDateTime, + IssuedAt = context.Principal.GetCreationDate()?.UtcDateTime, + Issuer = context.Issuer?.AbsoluteUri, + SigningCredentials = context.SigningCredentials, + Subject = (ClaimsIdentity) principal.Identity, + TokenType = context.TokenType switch { - Claims = claims, - EncryptingCredentials = context.EncryptionCredentials, - Expires = context.Principal.GetExpirationDate()?.UtcDateTime, - IssuedAt = context.Principal.GetCreationDate()?.UtcDateTime, - Issuer = context.Issuer?.AbsoluteUri, - SigningCredentials = context.SigningCredentials, - Subject = (ClaimsIdentity) principal.Identity, - TokenType = context.TokenType switch - { - null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), + null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), - TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken, - TokenTypeHints.IdToken => JsonWebTokenTypes.IdentityToken, - TokenTypeHints.AuthorizationCode => JsonWebTokenTypes.Private.AuthorizationCode, - TokenTypeHints.DeviceCode => JsonWebTokenTypes.Private.DeviceCode, - TokenTypeHints.RefreshToken => JsonWebTokenTypes.Private.RefreshToken, - TokenTypeHints.UserCode => JsonWebTokenTypes.Private.UserCode, + TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken, + TokenTypeHints.IdToken => JsonWebTokenTypes.IdentityToken, + TokenTypeHints.AuthorizationCode => JsonWebTokenTypes.Private.AuthorizationCode, + TokenTypeHints.DeviceCode => JsonWebTokenTypes.Private.DeviceCode, + TokenTypeHints.RefreshToken => JsonWebTokenTypes.Private.RefreshToken, + TokenTypeHints.UserCode => JsonWebTokenTypes.Private.UserCode, - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) - } - }; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) + } + }; - context.Token = context.SecurityTokenHandler.CreateToken(descriptor); + context.Token = context.SecurityTokenHandler.CreateToken(descriptor); - context.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType, context.Token, principal.Claims); + context.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType, context.Token, principal.Claims); - return default; - } + return default; } + } + + /// + /// Contains the logic responsible of converting the token to a reference token. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ConvertReferenceToken : IOpenIddictServerHandler + { + private readonly IOpenIddictTokenManager _tokenManager; + + public ConvertReferenceToken() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ConvertReferenceToken(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// - /// Contains the logic responsible of converting the token to a reference token. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ConvertReferenceToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(GenerateIdentityModelToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(GenerateTokenContext context) { - private readonly IOpenIddictTokenManager _tokenManager; - - public ConvertReferenceToken() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ConvertReferenceToken(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(GenerateIdentityModelToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(GenerateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var identifier = context.Principal.GetTokenId(); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0009)); - } + var identifier = context.Principal.GetTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0009)); + } - var token = await _tokenManager.FindByIdAsync(identifier); - if (token is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); - } + var token = await _tokenManager.FindByIdAsync(identifier); + if (token is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); + } - var descriptor = new OpenIddictTokenDescriptor(); - await _tokenManager.PopulateAsync(descriptor, token); + var descriptor = new OpenIddictTokenDescriptor(); + await _tokenManager.PopulateAsync(descriptor, token); - // Attach the generated token to the token entry. - descriptor.Payload = context.Token; - descriptor.Principal = context.Principal; + // Attach the generated token to the token entry. + descriptor.Payload = context.Token; + descriptor.Principal = context.Principal; - // Note: unlike other reference tokens, user codes are meant to be used by humans, - // who may have to enter it in a web form. To ensure it remains easy enough to type - // even by users with non-Latin keyboards, user codes generated by OpenIddict are - // only compound of 12 digits, generated using a crypto-secure random number generator. - // In this case, the resulting user code is estimated to have at most ~40 bits of entropy. - if (context.TokenType is TokenTypeHints.UserCode) + // Note: unlike other reference tokens, user codes are meant to be used by humans, + // who may have to enter it in a web form. To ensure it remains easy enough to type + // even by users with non-Latin keyboards, user codes generated by OpenIddict are + // only compound of 12 digits, generated using a crypto-secure random number generator. + // In this case, the resulting user code is estimated to have at most ~40 bits of entropy. + if (context.TokenType is TokenTypeHints.UserCode) + { + do { - do - { - var data = new byte[12]; + var data = new byte[12]; #if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); + RandomNumberGenerator.Fill(data); #else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); + using var generator = RandomNumberGenerator.Create(); + generator.GetBytes(data); #endif - var builder = new StringBuilder(data.Length); + var builder = new StringBuilder(data.Length); - for (var index = 0; index < data.Length; index += 4) - { - builder.AppendFormat(CultureInfo.InvariantCulture, "{0:D4}", BitConverter.ToUInt32(data, index) % 10000); - } - - descriptor.ReferenceId = builder.ToString(); + for (var index = 0; index < data.Length; index += 4) + { + builder.AppendFormat(CultureInfo.InvariantCulture, "{0:D4}", BitConverter.ToUInt32(data, index) % 10000); } - // User codes are relatively short. To help reduce the risks of collisions with - // existing entries, a database check is performed here before updating the entry. - while (await _tokenManager.FindByReferenceIdAsync(descriptor.ReferenceId) is not null); + descriptor.ReferenceId = builder.ToString(); } - // For other tokens, generate a base64url-encoded 256-bit random identifier. - else - { - var data = new byte[256 / 8]; + // User codes are relatively short. To help reduce the risks of collisions with + // existing entries, a database check is performed here before updating the entry. + while (await _tokenManager.FindByReferenceIdAsync(descriptor.ReferenceId) is not null); + } + + // For other tokens, generate a base64url-encoded 256-bit random identifier. + else + { + var data = new byte[256 / 8]; #if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS - RandomNumberGenerator.Fill(data); + RandomNumberGenerator.Fill(data); #else - using var generator = RandomNumberGenerator.Create(); - generator.GetBytes(data); + using var generator = RandomNumberGenerator.Create(); + generator.GetBytes(data); #endif - descriptor.ReferenceId = Base64UrlEncoder.Encode(data); - } + descriptor.ReferenceId = Base64UrlEncoder.Encode(data); + } - await _tokenManager.UpdateAsync(token, descriptor); + await _tokenManager.UpdateAsync(token, descriptor); - // Replace the returned token by the reference identifier. - context.Token = descriptor.ReferenceId; + // Replace the returned token by the reference identifier. + context.Token = descriptor.ReferenceId; - context.Logger.LogTrace(SR.GetResourceString(SR.ID6014), context.TokenType, identifier, descriptor.ReferenceId); - } + context.Logger.LogTrace(SR.GetResourceString(SR.ID6014), context.TokenType, identifier, descriptor.ReferenceId); } + } + /// + /// Contains the logic responsible of beautifying user-typed tokens. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class BeautifyToken : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of beautifying user-typed tokens. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class BeautifyToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + // Technically, this handler doesn't require that the degraded mode be disabled + // but the default CreateReferenceEntry handler that creates the user code + // reference identifiers only works when the degraded mode is disabled. + .AddFilter() + .UseSingletonHandler() + .SetOrder(ConvertReferenceToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(GenerateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - // Technically, this handler doesn't require that the degraded mode be disabled - // but the default CreateReferenceEntry handler that creates the user code - // reference identifiers only works when the degraded mode is disabled. - .AddFilter() - .UseSingletonHandler() - .SetOrder(ConvertReferenceToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(GenerateTokenContext context) - { - if (context is null) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + // To make user codes easier to read and type by humans, a dash is automatically + // appended before each new block of 4 integers. These dashes are expected to be + // stripped from the user codes when receiving them at the verification endpoint. + if (context.TokenType is TokenTypeHints.UserCode) + { + var builder = new StringBuilder(context.Token); + if (builder.Length % 4 != 0) { - throw new ArgumentNullException(nameof(context)); + return default; } - // To make user codes easier to read and type by humans, a dash is automatically - // appended before each new block of 4 integers. These dashes are expected to be - // stripped from the user codes when receiving them at the verification endpoint. - if (context.TokenType is TokenTypeHints.UserCode) + for (var index = builder.Length; index >= 0; index -= 4) { - var builder = new StringBuilder(context.Token); - if (builder.Length % 4 != 0) + if (index != 0 && index != builder.Length) { - return default; + builder.Insert(index, Separators.Dash[0]); } - - for (var index = builder.Length; index >= 0; index -= 4) - { - if (index != 0 && index != builder.Length) - { - builder.Insert(index, Separators.Dash[0]); - } - } - - context.Token = builder.ToString(); } - return default; + context.Token = builder.ToString(); } + + return default; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs index bbdb3519..d1d77db4 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs @@ -16,927 +16,926 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerHandlers { - public static partial class OpenIddictServerHandlers + public static class Revocation { - public static class Revocation + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Revocation request top-level processing: + */ + ExtractRevocationRequest.Descriptor, + ValidateRevocationRequest.Descriptor, + HandleRevocationRequest.Descriptor, + ApplyRevocationResponse.Descriptor, + ApplyRevocationResponse.Descriptor, + + /* + * Revocation request validation: + */ + ValidateTokenParameter.Descriptor, + ValidateClientIdParameter.Descriptor, + ValidateClientId.Descriptor, + ValidateClientType.Descriptor, + ValidateClientSecret.Descriptor, + ValidateEndpointPermissions.Descriptor, + ValidateToken.Descriptor, + ValidateTokenType.Descriptor, + ValidateAuthorizedParty.Descriptor, + + /* + * Revocation request handling: + */ + AttachPrincipal.Descriptor, + RevokeToken.Descriptor, + + /* + * Revocation response handling: + */ + NormalizeErrorResponse.Descriptor); + + /// + /// Contains the logic responsible of extracting revocation requests and invoking the corresponding event handlers. + /// + public class ExtractRevocationRequest : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Revocation request top-level processing: - */ - ExtractRevocationRequest.Descriptor, - ValidateRevocationRequest.Descriptor, - HandleRevocationRequest.Descriptor, - ApplyRevocationResponse.Descriptor, - ApplyRevocationResponse.Descriptor, - - /* - * Revocation request validation: - */ - ValidateTokenParameter.Descriptor, - ValidateClientIdParameter.Descriptor, - ValidateClientId.Descriptor, - ValidateClientType.Descriptor, - ValidateClientSecret.Descriptor, - ValidateEndpointPermissions.Descriptor, - ValidateToken.Descriptor, - ValidateTokenType.Descriptor, - ValidateAuthorizedParty.Descriptor, - - /* - * Revocation request handling: - */ - AttachPrincipal.Descriptor, - RevokeToken.Descriptor, - - /* - * Revocation response handling: - */ - NormalizeErrorResponse.Descriptor); + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractRevocationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting revocation requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractRevocationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractRevocationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ExtractRevocationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ExtractRevocationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0048)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6109), notification.Request); + if (notification.Request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0048)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6109), notification.Request); } + } + + /// + /// Contains the logic responsible of validating revocation requests and invoking the corresponding event handlers. + /// + public class ValidateRevocationRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateRevocationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating revocation requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateRevocationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractRevocationRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateRevocationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractRevocationRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ValidateRevocationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the principal without triggering a new validation process. - context.Transaction.SetProperty(typeof(ValidateRevocationRequestContext).FullName!, notification); + var notification = new ValidateRevocationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the principal without triggering a new validation process. + context.Transaction.SetProperty(typeof(ValidateRevocationRequestContext).FullName!, notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6110)); + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6110)); } + } + + /// + /// Contains the logic responsible of handling revocation requests and invoking the corresponding event handlers. + /// + public class HandleRevocationRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleRevocationRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling revocation requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleRevocationRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateRevocationRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleRevocationRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateRevocationRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new HandleRevocationRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new HandleRevocationRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.Transaction.Response = new OpenIddictResponse(); + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.Transaction.Response = new OpenIddictResponse(); } + } + + /// + /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// + public class ApplyRevocationResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ApplyRevocationResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyRevocationResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyRevocationResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ApplyRevocationResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ApplyRevocationResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0049)); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0049)); } + } + /// + /// Contains the logic responsible of rejecting revocation requests that don't specify a token. + /// + public class ValidateTokenParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting revocation requests that don't specify a token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTokenParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateRevocationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateRevocationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reject revocation requests missing the mandatory token parameter. - if (string.IsNullOrEmpty(context.Request.Token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6111), Parameters.Token); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2029(Parameters.Token), - uri: SR.FormatID8000(SR.ID2029)); + // Reject revocation requests missing the mandatory token parameter. + if (string.IsNullOrEmpty(context.Request.Token)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6111), Parameters.Token); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2029(Parameters.Token), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting revocation requests that don't specify a client identifier. + /// + public class ValidateClientIdParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting revocation requests that don't specify a client identifier. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientIdParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateTokenParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateRevocationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateTokenParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateRevocationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // At this stage, reject the revocation request unless the client identification requirement was disabled. - if (!context.Options.AcceptAnonymousClients && string.IsNullOrEmpty(context.ClientId)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6111), Parameters.ClientId); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2029(Parameters.ClientId), - uri: SR.FormatID8000(SR.ID2029)); + // At this stage, reject the revocation request unless the client identification requirement was disabled. + if (!context.Options.AcceptAnonymousClients && string.IsNullOrEmpty(context.ClientId)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6111), Parameters.ClientId); - return default; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2029(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2029)); return default; } - } - /// - /// Contains the logic responsible of rejecting revocation requests that use an invalid client_id. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateClientId : IOpenIddictServerHandler - { - private readonly IOpenIddictApplicationManager _applicationManager; + return default; + } + } - public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of rejecting revocation requests that use an invalid client_id. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientId : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - public ValidateClientId(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public ValidateClientId(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; - /// - public async ValueTask HandleAsync(ValidateRevocationRequestContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateRevocationRequestContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - // Retrieve the application details corresponding to the requested client_id. - // If no entity can be found, this likely indicates that the client_id is invalid. - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6112), context.ClientId); + // Retrieve the application details corresponding to the requested client_id. + // If no entity can be found, this likely indicates that the client_id is invalid. + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6112), context.ClientId); - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2052(Parameters.ClientId), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2052(Parameters.ClientId), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting revocation requests made by applications + /// whose client type is not compatible with the presence or absence of a client secret. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientType : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateClientType(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting revocation requests made by applications - /// whose client type is not compatible with the presence or absence of a client secret. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientType : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientId.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateRevocationRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateClientType(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientId.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - /// - public async ValueTask HandleAsync(ValidateRevocationRequestContext context) + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { - if (context is null) + // Reject revocation requests containing a client_secret when the client is a public application. + if (!string.IsNullOrEmpty(context.ClientSecret)) { - throw new ArgumentNullException(nameof(context)); - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6113), context.ClientId); - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2053(Parameters.ClientSecret), + uri: SR.FormatID8000(SR.ID2053)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + return; } - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) - { - // Reject revocation requests containing a client_secret when the client is a public application. - if (!string.IsNullOrEmpty(context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6113), context.ClientId); + return; + } - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2053(Parameters.ClientSecret), - uri: SR.FormatID8000(SR.ID2053)); + // Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks. + if (string.IsNullOrEmpty(context.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6114), context.ClientId); - return; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.FormatID2054(Parameters.ClientSecret), + uri: SR.FormatID8000(SR.ID2054)); - return; - } + return; + } + } + } - // Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks. - if (string.IsNullOrEmpty(context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6114), context.ClientId); + /// + /// Contains the logic responsible of rejecting revocation requests specifying an invalid client secret. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientSecret : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - context.Reject( - error: Errors.InvalidClient, - description: SR.FormatID2054(Parameters.ClientSecret), - uri: SR.FormatID8000(SR.ID2054)); + public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - return; - } - } - } + public ValidateClientSecret(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting revocation requests specifying an invalid client secret. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientSecret : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateRevocationRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateClientSecret(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - /// - public async ValueTask HandleAsync(ValidateRevocationRequestContext context) + // If the application is a public client, don't validate the client secret. + if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return; + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6115), context.ClientId); - // If the application is a public client, don't validate the client secret. - if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public)) - { - return; - } + context.Reject( + error: Errors.InvalidClient, + description: SR.GetResourceString(SR.ID2055), + uri: SR.FormatID8000(SR.ID2055)); - Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret)); + return; + } + } + } - if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6115), context.ClientId); + /// + /// Contains the logic responsible of rejecting revocation requests made by + /// applications that haven't been granted the revocation endpoint permission. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateEndpointPermissions : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; - context.Reject( - error: Errors.InvalidClient, - description: SR.GetResourceString(SR.ID2055), - uri: SR.FormatID8000(SR.ID2055)); + public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - return; - } - } - } + public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting revocation requests made by - /// applications that haven't been granted the revocation endpoint permission. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateEndpointPermissions : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateClientSecret.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateRevocationRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateClientSecret.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateRevocationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - var application = await _applicationManager.FindByClientIdAsync(context.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); - } + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); + if (application is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0032)); + } - // Reject the request if the application is not allowed to use the revocation endpoint. - if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Revocation)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6116), context.ClientId); + // Reject the request if the application is not allowed to use the revocation endpoint. + if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Revocation)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6116), context.ClientId); - context.Reject( - error: Errors.UnauthorizedClient, - description: SR.GetResourceString(SR.ID2078), - uri: SR.FormatID8000(SR.ID2078)); + context.Reject( + error: Errors.UnauthorizedClient, + description: SR.GetResourceString(SR.ID2078), + uri: SR.FormatID8000(SR.ID2078)); - return; - } + return; } } + } + + /// + /// Contains the logic responsible of rejecting revocation requests that don't specify a valid token. + /// + public class ValidateToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of rejecting revocation requests that don't specify a valid token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateRevocationRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseScopedHandler() - .SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateRevocationRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ProcessAuthenticationContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the authentication result without triggering a new authentication flow. - context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); + var notification = new ProcessAuthenticationContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the authentication result without triggering a new authentication flow. + context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - // Attach the security principal extracted from the token to the validation context. - context.Principal = notification.GenericTokenPrincipal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + // Attach the security principal extracted from the token to the validation context. + context.Principal = notification.GenericTokenPrincipal; } + } + /// + /// Contains the logic responsible of rejecting revocation requests that specify an unsupported token. + /// + public class ValidateTokenType : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting revocation requests that specify an unsupported token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTokenType : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateRevocationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateRevocationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) && - !context.Principal.HasTokenType(TokenTypeHints.RefreshToken)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6117)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.Reject( - error: Errors.UnsupportedTokenType, - description: SR.GetResourceString(SR.ID2079), - uri: SR.FormatID8000(SR.ID2079)); + if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) && + !context.Principal.HasTokenType(TokenTypeHints.RefreshToken)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6117)); - return default; - } + context.Reject( + error: Errors.UnsupportedTokenType, + description: SR.GetResourceString(SR.ID2079), + uri: SR.FormatID8000(SR.ID2079)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting revocation requests that specify a token + /// that cannot be revoked by the client application sending the revocation requests. + /// + public class ValidateAuthorizedParty : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting revocation requests that specify a token - /// that cannot be revoked by the client application sending the revocation requests. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAuthorizedParty : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + // Note: when client identification is not enforced, this handler cannot validate + // the audiences/presenters if the client_id of the calling application is not known. + // In this case, the risk is quite limited as claims are never returned by this endpoint. + .AddFilter() + .UseSingletonHandler() + .SetOrder(ValidateTokenType.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateRevocationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - // Note: when client identification is not enforced, this handler cannot validate - // the audiences/presenters if the client_id of the calling application is not known. - // In this case, the risk is quite limited as claims are never returned by this endpoint. - .AddFilter() - .UseSingletonHandler() - .SetOrder(ValidateTokenType.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateRevocationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // When the revoked token is an access token, the caller must be listed either as a presenter - // (i.e the party the token was issued to) or as an audience (i.e a resource server/API). - // If the access token doesn't contain any explicit presenter/audience, the token is assumed - // to be not specific to any resource server/client application and the check is bypassed. - if (context.Principal.HasTokenType(TokenTypeHints.AccessToken) && - context.Principal.HasClaim(Claims.Private.Audience) && !context.Principal.HasAudience(context.ClientId) && - context.Principal.HasClaim(Claims.Private.Presenter) && !context.Principal.HasPresenter(context.ClientId)) - { - context.Logger.LogWarning(SR.GetResourceString(SR.ID6119)); + Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2080), - uri: SR.FormatID8000(SR.ID2080)); + // When the revoked token is an access token, the caller must be listed either as a presenter + // (i.e the party the token was issued to) or as an audience (i.e a resource server/API). + // If the access token doesn't contain any explicit presenter/audience, the token is assumed + // to be not specific to any resource server/client application and the check is bypassed. + if (context.Principal.HasTokenType(TokenTypeHints.AccessToken) && + context.Principal.HasClaim(Claims.Private.Audience) && !context.Principal.HasAudience(context.ClientId) && + context.Principal.HasClaim(Claims.Private.Presenter) && !context.Principal.HasPresenter(context.ClientId)) + { + context.Logger.LogWarning(SR.GetResourceString(SR.ID6119)); - return default; - } + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2080), + uri: SR.FormatID8000(SR.ID2080)); - // When the revoked token is a refresh token, the caller must be - // listed as a presenter (i.e the party the token was issued to). - // If the refresh token doesn't contain any explicit presenter, the token is - // assumed to be not specific to any client application and the check is bypassed. - if (context.Principal.HasTokenType(TokenTypeHints.RefreshToken) && - context.Principal.HasClaim(Claims.Private.Presenter) && !context.Principal.HasPresenter(context.ClientId)) - { - context.Logger.LogWarning(SR.GetResourceString(SR.ID6121)); + return default; + } - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2080), - uri: SR.FormatID8000(SR.ID2080)); + // When the revoked token is a refresh token, the caller must be + // listed as a presenter (i.e the party the token was issued to). + // If the refresh token doesn't contain any explicit presenter, the token is + // assumed to be not specific to any client application and the check is bypassed. + if (context.Principal.HasTokenType(TokenTypeHints.RefreshToken) && + context.Principal.HasClaim(Claims.Private.Presenter) && !context.Principal.HasPresenter(context.ClientId)) + { + context.Logger.LogWarning(SR.GetResourceString(SR.ID6121)); - return default; - } + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2080), + uri: SR.FormatID8000(SR.ID2080)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of attaching the principal + /// extracted from the revoked token to the event context. + /// + public class AttachPrincipal : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the principal - /// extracted from the revoked token to the event context. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachPrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleRevocationRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleRevocationRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var notification = context.Transaction.GetProperty( - typeof(ValidateRevocationRequestContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); + var notification = context.Transaction.GetProperty( + typeof(ValidateRevocationRequestContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); - Debug.Assert(notification.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(notification.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.Principal ??= notification.Principal; + context.Principal ??= notification.Principal; - return default; - } + return default; } + } - /// - /// Contains the logic responsible of revoking the token sent by the client application. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class RevokeToken : IOpenIddictServerHandler - { - private readonly IOpenIddictTokenManager _tokenManager; - - public RevokeToken() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + /// + /// Contains the logic responsible of revoking the token sent by the client application. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class RevokeToken : IOpenIddictServerHandler + { + private readonly IOpenIddictTokenManager _tokenManager; - public RevokeToken(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + public RevokeToken() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(AttachPrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + public RevokeToken(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; - /// - public async ValueTask HandleAsync(HandleRevocationRequestContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(AttachPrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(HandleRevocationRequestContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // Extract the token identifier from the authentication principal. - var identifier = context.Principal.GetTokenId(); - if (string.IsNullOrEmpty(identifier)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6122)); + // Extract the token identifier from the authentication principal. + var identifier = context.Principal.GetTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6122)); - context.Reject( - error: Errors.UnsupportedTokenType, - description: SR.GetResourceString(SR.ID2079), - uri: SR.FormatID8000(SR.ID2079)); + context.Reject( + error: Errors.UnsupportedTokenType, + description: SR.GetResourceString(SR.ID2079), + uri: SR.FormatID8000(SR.ID2079)); - return; - } + return; + } - var token = await _tokenManager.FindByIdAsync(identifier); - if (token is null) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6123), identifier); + var token = await _tokenManager.FindByIdAsync(identifier); + if (token is null) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6123), identifier); - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2004), - uri: SR.FormatID8000(SR.ID2004)); + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2004), + uri: SR.FormatID8000(SR.ID2004)); - return; - } + return; + } - // Try to revoke the token. If an error occurs, return an error. - if (!await _tokenManager.TryRevokeAsync(token)) - { - context.Reject( - error: Errors.UnsupportedTokenType, - description: SR.GetResourceString(SR.ID2079), - uri: SR.FormatID8000(SR.ID2079)); + // Try to revoke the token. If an error occurs, return an error. + if (!await _tokenManager.TryRevokeAsync(token)) + { + context.Reject( + error: Errors.UnsupportedTokenType, + description: SR.GetResourceString(SR.ID2079), + uri: SR.FormatID8000(SR.ID2079)); - return; - } + return; } } + } + /// + /// Contains the logic responsible of converting revocation errors to standard empty responses. + /// + public class NormalizeErrorResponse : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of converting revocation errors to standard empty responses. + /// Gets the default descriptor definition assigned to this handler. /// - public class NormalizeErrorResponse : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyRevocationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyRevocationResponseContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (string.IsNullOrEmpty(context.Error)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If the error indicates an invalid token, remove the error details, as required by the revocation - // specification. Visit https://tools.ietf.org/html/rfc7009#section-2.2 for more information. - // While this prevent the resource server from determining the root cause of the revocation failure, - // this is required to keep OpenIddict fully standard and compatible with all revocation clients. + if (string.IsNullOrEmpty(context.Error)) + { + return default; + } - if (string.Equals(context.Error, Errors.InvalidToken, StringComparison.Ordinal)) - { - context.Response.Error = null; - context.Response.ErrorDescription = null; - context.Response.ErrorUri = null; - } + // If the error indicates an invalid token, remove the error details, as required by the revocation + // specification. Visit https://tools.ietf.org/html/rfc7009#section-2.2 for more information. + // While this prevent the resource server from determining the root cause of the revocation failure, + // this is required to keep OpenIddict fully standard and compatible with all revocation clients. - return default; + if (string.Equals(context.Error, Errors.InvalidToken, StringComparison.Ordinal)) + { + context.Response.Error = null; + context.Response.ErrorDescription = null; + context.Response.ErrorUri = null; } + + return default; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs index 2e6c4a73..f82bc5ab 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs @@ -15,499 +15,498 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerHandlers { - public static partial class OpenIddictServerHandlers + public static class Session { - public static class Session + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Logout request top-level processing: + */ + ExtractLogoutRequest.Descriptor, + ValidateLogoutRequest.Descriptor, + HandleLogoutRequest.Descriptor, + ApplyLogoutResponse.Descriptor, + ApplyLogoutResponse.Descriptor, + ApplyLogoutResponse.Descriptor, + + /* + * Logout request validation: + */ + ValidatePostLogoutRedirectUriParameter.Descriptor, + ValidateClientPostLogoutRedirectUri.Descriptor, + + /* + * Logout response processing: + */ + AttachPostLogoutRedirectUri.Descriptor, + AttachResponseState.Descriptor); + + /// + /// Contains the logic responsible of extracting logout requests and invoking the corresponding event handlers. + /// + public class ExtractLogoutRequest : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Logout request top-level processing: - */ - ExtractLogoutRequest.Descriptor, - ValidateLogoutRequest.Descriptor, - HandleLogoutRequest.Descriptor, - ApplyLogoutResponse.Descriptor, - ApplyLogoutResponse.Descriptor, - ApplyLogoutResponse.Descriptor, - - /* - * Logout request validation: - */ - ValidatePostLogoutRedirectUriParameter.Descriptor, - ValidateClientPostLogoutRedirectUri.Descriptor, - - /* - * Logout response processing: - */ - AttachPostLogoutRedirectUri.Descriptor, - AttachResponseState.Descriptor); + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractLogoutRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting logout requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractLogoutRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractLogoutRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ExtractLogoutRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + var notification = new ExtractLogoutRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0050)); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6124), notification.Request); + if (notification.Request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0050)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6124), notification.Request); } + } + + /// + /// Contains the logic responsible of validating logout requests and invoking the corresponding event handlers. + /// + public class ValidateLogoutRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateLogoutRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating logout requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateLogoutRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractLogoutRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateLogoutRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractLogoutRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ValidateLogoutRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + throw new ArgumentNullException(nameof(context)); + } - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the redirect_uri without triggering a new validation process. - context.Transaction.SetProperty(typeof(ValidateLogoutRequestContext).FullName!, notification); + var notification = new ValidateLogoutRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the redirect_uri without triggering a new validation process. + context.Transaction.SetProperty(typeof(ValidateLogoutRequestContext).FullName!, notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6125)); + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6125)); } + } + + /// + /// Contains the logic responsible of handling logout requests and invoking the corresponding event handlers. + /// + public class HandleLogoutRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleLogoutRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling logout requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleLogoutRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateLogoutRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleLogoutRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateLogoutRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var notification = new HandleLogoutRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } + + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } + + else if (notification.IsRejected) { - if (context is null) + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } + + if (notification.IsSignOutTriggered) + { + var @event = new ProcessSignOutContext(context.Transaction) { - throw new ArgumentNullException(nameof(context)); + Response = new OpenIddictResponse() + }; + + if (notification.Parameters.Count > 0) + { + foreach (var parameter in notification.Parameters) + { + @event.Parameters.Add(parameter.Key, parameter.Value); + } } - var notification = new HandleLogoutRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + await _dispatcher.DispatchAsync(@event); - if (notification.IsRequestHandled) + if (@event.IsRequestHandled) { context.HandleRequest(); return; } - else if (notification.IsRequestSkipped) + else if (@event.IsRequestSkipped) { context.SkipRequest(); return; } - else if (notification.IsRejected) + else if (@event.IsRejected) { context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); + error: @event.Error ?? Errors.InvalidRequest, + description: @event.ErrorDescription, + uri: @event.ErrorUri); return; } + } - if (notification.IsSignOutTriggered) - { - var @event = new ProcessSignOutContext(context.Transaction) - { - Response = new OpenIddictResponse() - }; - - if (notification.Parameters.Count > 0) - { - foreach (var parameter in notification.Parameters) - { - @event.Parameters.Add(parameter.Key, parameter.Value); - } - } - - await _dispatcher.DispatchAsync(@event); - - if (@event.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (@event.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0051)); + } + } - else if (@event.IsRejected) - { - context.Reject( - error: @event.Error ?? Errors.InvalidRequest, - description: @event.ErrorDescription, - uri: @event.ErrorUri); - return; - } - } + /// + /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// + public class ApplyLogoutResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; - throw new InvalidOperationException(SR.GetResourceString(SR.ID0051)); - } - } + public ApplyLogoutResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyLogoutResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyLogoutResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var notification = new ApplyLogoutResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); + var notification = new ApplyLogoutResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0052)); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0052)); } + } + /// + /// Contains the logic responsible of rejecting logout requests that specify an invalid post_logout_redirect_uri parameter. + /// + public class ValidatePostLogoutRedirectUriParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting logout requests that specify an invalid post_logout_redirect_uri parameter. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidatePostLogoutRedirectUriParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateLogoutRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateLogoutRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) - { - return default; - } + throw new ArgumentNullException(nameof(context)); + } - // If an optional post_logout_redirect_uri was provided, validate it. - if (!Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6126), Parameters.PostLogoutRedirectUri, context.PostLogoutRedirectUri); + if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) + { + return default; + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2030(Parameters.PostLogoutRedirectUri), - uri: SR.FormatID8000(SR.ID2030)); + // If an optional post_logout_redirect_uri was provided, validate it. + if (!Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6126), Parameters.PostLogoutRedirectUri, context.PostLogoutRedirectUri); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2030(Parameters.PostLogoutRedirectUri), + uri: SR.FormatID8000(SR.ID2030)); - if (!string.IsNullOrEmpty(uri.Fragment)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6127), Parameters.PostLogoutRedirectUri, context.PostLogoutRedirectUri); + return default; + } - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2031(Parameters.PostLogoutRedirectUri), - uri: SR.FormatID8000(SR.ID2031)); + if (!string.IsNullOrEmpty(uri.Fragment)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6127), Parameters.PostLogoutRedirectUri, context.PostLogoutRedirectUri); - return default; - } + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2031(Parameters.PostLogoutRedirectUri), + uri: SR.FormatID8000(SR.ID2031)); return default; } + + return default; } + } + + /// + /// Contains the logic responsible of rejecting logout requests that use an invalid redirect_uri. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateClientPostLogoutRedirectUri : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + + public ValidateClientPostLogoutRedirectUri() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public ValidateClientPostLogoutRedirectUri(IOpenIddictApplicationManager applicationManager) + => _applicationManager = applicationManager; /// - /// Contains the logic responsible of rejecting logout requests that use an invalid redirect_uri. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateClientPostLogoutRedirectUri : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidatePostLogoutRedirectUriParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateLogoutRequestContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - - public ValidateClientPostLogoutRedirectUri() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public ValidateClientPostLogoutRedirectUri(IOpenIddictApplicationManager applicationManager) - => _applicationManager = applicationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidatePostLogoutRedirectUriParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateLogoutRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(!string.IsNullOrEmpty(context.PostLogoutRedirectUri), SR.FormatID4000(Parameters.PostLogoutRedirectUri)); + Debug.Assert(!string.IsNullOrEmpty(context.PostLogoutRedirectUri), SR.FormatID4000(Parameters.PostLogoutRedirectUri)); - if (!await ValidatePostLogoutRedirectUriAsync(context.PostLogoutRedirectUri)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6128), context.PostLogoutRedirectUri); + if (!await ValidatePostLogoutRedirectUriAsync(context.PostLogoutRedirectUri)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6128), context.PostLogoutRedirectUri); - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2052(Parameters.PostLogoutRedirectUri), - uri: SR.FormatID8000(SR.ID2052)); + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2052(Parameters.PostLogoutRedirectUri), + uri: SR.FormatID8000(SR.ID2052)); - return; - } + return; + } - async ValueTask ValidatePostLogoutRedirectUriAsync(string address) - { - // To be considered valid, a post_logout_redirect_uri must correspond to an existing client application - // that was granted the ept:logout permission, unless endpoint permissions checking was explicitly disabled. + async ValueTask ValidatePostLogoutRedirectUriAsync(string address) + { + // To be considered valid, a post_logout_redirect_uri must correspond to an existing client application + // that was granted the ept:logout permission, unless endpoint permissions checking was explicitly disabled. - await foreach (var application in _applicationManager.FindByPostLogoutRedirectUriAsync(address)) + await foreach (var application in _applicationManager.FindByPostLogoutRedirectUriAsync(address)) + { + if (context.Options.IgnoreEndpointPermissions || + await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Logout)) { - if (context.Options.IgnoreEndpointPermissions || - await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Logout)) - { - return true; - } + return true; } - - return false; } + + return false; } } + } + /// + /// Contains the logic responsible of inferring the redirect URL + /// used to send the response back to the client application. + /// + public class AttachPostLogoutRedirectUri : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of inferring the redirect URL - /// used to send the response back to the client application. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachPostLogoutRedirectUri : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyLogoutResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyLogoutResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.Request is null) - { - return default; - } + throw new ArgumentNullException(nameof(context)); + } - var notification = context.Transaction.GetProperty( - typeof(ValidateLogoutRequestContext).FullName!); + if (context.Request is null) + { + return default; + } - // Note: at this stage, the validated redirect URI property may be null (e.g if - // an error is returned from the ExtractLogoutRequest/ValidateLogoutRequest events). - if (notification is not null && !notification.IsRejected) - { - context.PostLogoutRedirectUri = notification.PostLogoutRedirectUri; - } + var notification = context.Transaction.GetProperty( + typeof(ValidateLogoutRequestContext).FullName!); - return default; + // Note: at this stage, the validated redirect URI property may be null (e.g if + // an error is returned from the ExtractLogoutRequest/ValidateLogoutRequest events). + if (notification is not null && !notification.IsRejected) + { + context.PostLogoutRedirectUri = notification.PostLogoutRedirectUri; } + + return default; } + } + /// + /// Contains the logic responsible of attaching the state to the response. + /// + public class AttachResponseState : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the state to the response. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachResponseState : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachPostLogoutRedirectUri.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyLogoutResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachPostLogoutRedirectUri.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ApplyLogoutResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Attach the request state to the logout response. - if (string.IsNullOrEmpty(context.Response.State)) - { - context.Response.State = context.Request?.State; - } + throw new ArgumentNullException(nameof(context)); + } - return default; + // Attach the request state to the logout response. + if (string.IsNullOrEmpty(context.Response.State)) + { + context.Response.State = context.Request?.State; } + + return default; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs index 77e8ed2c..2565d2cb 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs @@ -17,518 +17,517 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +public static partial class OpenIddictServerHandlers { - public static partial class OpenIddictServerHandlers + public static class Userinfo { - public static class Userinfo + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Userinfo request top-level processing: + */ + ExtractUserinfoRequest.Descriptor, + ValidateUserinfoRequest.Descriptor, + HandleUserinfoRequest.Descriptor, + ApplyUserinfoResponse.Descriptor, + ApplyUserinfoResponse.Descriptor, + ApplyUserinfoResponse.Descriptor, + + /* + * Userinfo request validation: + */ + ValidateAccessTokenParameter.Descriptor, + ValidateToken.Descriptor, + + /* + * Userinfo request handling: + */ + AttachPrincipal.Descriptor, + AttachAudiences.Descriptor, + AttachClaims.Descriptor); + + /// + /// Contains the logic responsible of extracting userinfo requests and invoking the corresponding event handlers. + /// + public class ExtractUserinfoRequest : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Userinfo request top-level processing: - */ - ExtractUserinfoRequest.Descriptor, - ValidateUserinfoRequest.Descriptor, - HandleUserinfoRequest.Descriptor, - ApplyUserinfoResponse.Descriptor, - ApplyUserinfoResponse.Descriptor, - ApplyUserinfoResponse.Descriptor, - - /* - * Userinfo request validation: - */ - ValidateAccessTokenParameter.Descriptor, - ValidateToken.Descriptor, - - /* - * Userinfo request handling: - */ - AttachPrincipal.Descriptor, - AttachAudiences.Descriptor, - AttachClaims.Descriptor); + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ExtractUserinfoRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of extracting userinfo requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractUserinfoRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ExtractUserinfoRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var notification = new ExtractUserinfoRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } + + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } + + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } + + if (notification.Request is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ExtractUserinfoRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } - - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } - - if (notification.Request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0053)); - } - - context.Logger.LogInformation(SR.GetResourceString(SR.ID6129), notification.Request); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0053)); } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6129), notification.Request); } + } + + /// + /// Contains the logic responsible of validating userinfo requests and invoking the corresponding event handlers. + /// + public class ValidateUserinfoRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateUserinfoRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating userinfo requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateUserinfoRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtractUserinfoRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateUserinfoRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ExtractUserinfoRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var notification = new ValidateUserinfoRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); + + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the principal without triggering a new validation process. + context.Transaction.SetProperty(typeof(ValidateUserinfoRequestContext).FullName!, notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } + + else if (notification.IsRequestSkipped) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ValidateUserinfoRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); - - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the principal without triggering a new validation process. - context.Transaction.SetProperty(typeof(ValidateUserinfoRequestContext).FullName!, notification); - - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } - - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } - - context.Logger.LogInformation(SR.GetResourceString(SR.ID6130)); + context.SkipRequest(); + return; } + + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6130)); } + } + + /// + /// Contains the logic responsible of handling userinfo requests and invoking the corresponding event handlers. + /// + public class HandleUserinfoRequest : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public HandleUserinfoRequest(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of handling userinfo requests and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleUserinfoRequest : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateUserinfoRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public HandleUserinfoRequest(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateUserinfoRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessRequestContext context) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var notification = new HandleUserinfoRequestContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } + + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } + + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } + + var response = new OpenIddictResponse { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new HandleUserinfoRequestContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } - - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } - - var response = new OpenIddictResponse - { - [Claims.Subject] = notification.Subject, - [Claims.Address] = notification.Address, - [Claims.Birthdate] = notification.BirthDate, - [Claims.Email] = notification.Email, - [Claims.EmailVerified] = notification.EmailVerified, - [Claims.FamilyName] = notification.FamilyName, - [Claims.GivenName] = notification.GivenName, - [Claims.Issuer] = notification.Issuer?.AbsoluteUri, - [Claims.PhoneNumber] = notification.PhoneNumber, - [Claims.PhoneNumberVerified] = notification.PhoneNumberVerified, - [Claims.PreferredUsername] = notification.PreferredUsername, - [Claims.Profile] = notification.Profile, - [Claims.Website] = notification.Website - }; - - switch (notification.Audiences.Count) - { - case 0: break; - - case 1: - response[Claims.Audience] = notification.Audiences.ElementAt(0); - break; - - default: - response[Claims.Audience] = notification.Audiences.ToArray(); - break; - } - - foreach (var claim in notification.Claims) - { - response.SetParameter(claim.Key, claim.Value); - } - - context.Transaction.Response = response; + [Claims.Subject] = notification.Subject, + [Claims.Address] = notification.Address, + [Claims.Birthdate] = notification.BirthDate, + [Claims.Email] = notification.Email, + [Claims.EmailVerified] = notification.EmailVerified, + [Claims.FamilyName] = notification.FamilyName, + [Claims.GivenName] = notification.GivenName, + [Claims.Issuer] = notification.Issuer?.AbsoluteUri, + [Claims.PhoneNumber] = notification.PhoneNumber, + [Claims.PhoneNumberVerified] = notification.PhoneNumberVerified, + [Claims.PreferredUsername] = notification.PreferredUsername, + [Claims.Profile] = notification.Profile, + [Claims.Website] = notification.Website + }; + + switch (notification.Audiences.Count) + { + case 0: break; + + case 1: + response[Claims.Audience] = notification.Audiences.ElementAt(0); + break; + + default: + response[Claims.Audience] = notification.Audiences.ToArray(); + break; } + + foreach (var claim in notification.Claims) + { + response.SetParameter(claim.Key, claim.Value); + } + + context.Transaction.Response = response; } + } + + /// + /// Contains the logic responsible of processing userinfo responses and invoking the corresponding event handlers. + /// + public class ApplyUserinfoResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ApplyUserinfoResponse(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of processing userinfo responses and invoking the corresponding event handlers. + /// Gets the default descriptor definition assigned to this handler. /// - public class ApplyUserinfoResponse : IOpenIddictServerHandler where TContext : BaseRequestContext + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ApplyUserinfoResponse(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var notification = new ApplyUserinfoResponseContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } + + else if (notification.IsRequestSkipped) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ApplyUserinfoResponseContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } - - throw new InvalidOperationException(SR.GetResourceString(SR.ID0054)); + context.SkipRequest(); + return; } + + throw new InvalidOperationException(SR.GetResourceString(SR.ID0054)); } + } + /// + /// Contains the logic responsible of rejecting userinfo requests that don't specify an access token. + /// + public class ValidateAccessTokenParameter : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting userinfo requests that don't specify an access token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAccessTokenParameter : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateUserinfoRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateUserinfoRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (string.IsNullOrEmpty(context.Request.AccessToken)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6131), Parameters.AccessToken); + throw new ArgumentNullException(nameof(context)); + } - context.Reject( - error: Errors.MissingToken, - description: SR.FormatID2029(Parameters.AccessToken), - uri: SR.FormatID8000(SR.ID2029)); + if (string.IsNullOrEmpty(context.Request.AccessToken)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6131), Parameters.AccessToken); - return default; - } + context.Reject( + error: Errors.MissingToken, + description: SR.FormatID2029(Parameters.AccessToken), + uri: SR.FormatID8000(SR.ID2029)); return default; } + + return default; } + } + + /// + /// Contains the logic responsible of rejecting userinfo requests that don't specify a valid token. + /// + public class ValidateToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of rejecting userinfo requests that don't specify a valid token. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .SetOrder(ValidateAccessTokenParameter.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateUserinfoRequestContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseScopedHandler() - .SetOrder(ValidateAccessTokenParameter.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateUserinfoRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new ProcessAuthenticationContext(context.Transaction); - await _dispatcher.DispatchAsync(notification); - - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the authentication result without triggering a new authentication flow. - context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); - - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } - - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } - - // Attach the security principal extracted from the token to the validation context. - context.Principal = notification.AccessTokenPrincipal; + throw new ArgumentNullException(nameof(context)); } + + var notification = new ProcessAuthenticationContext(context.Transaction); + await _dispatcher.DispatchAsync(notification); + + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the authentication result without triggering a new authentication flow. + context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } + + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } + + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } + + // Attach the security principal extracted from the token to the validation context. + context.Principal = notification.AccessTokenPrincipal; } + } + /// + /// Contains the logic responsible of attaching the principal + /// extracted from the access token to the event context. + /// + public class AttachPrincipal : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the principal - /// extracted from the access token to the event context. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachPrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleUserinfoRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleUserinfoRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var notification = context.Transaction.GetProperty( - typeof(ValidateUserinfoRequestContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); + var notification = context.Transaction.GetProperty( + typeof(ValidateUserinfoRequestContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); - Debug.Assert(notification.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(notification.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.Principal ??= notification.Principal; + context.Principal ??= notification.Principal; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the audiences to the userinfo response. + /// + public class AttachAudiences : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the audiences to the userinfo response. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachAudiences : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachPrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleUserinfoRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachPrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleUserinfoRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // Note: when receiving an access token, its audiences list cannot be used for the "aud" claim - // as the client application is not the intented audience but only an authorized presenter. - // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse - context.Audiences.UnionWith(context.Principal.GetPresenters()); + // Note: when receiving an access token, its audiences list cannot be used for the "aud" claim + // as the client application is not the intented audience but only an authorized presenter. + // See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse + context.Audiences.UnionWith(context.Principal.GetPresenters()); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching well known claims to the userinfo response. + /// + public class AttachClaims : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching well known claims to the userinfo response. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachClaims : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachAudiences.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleUserinfoRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachAudiences.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleUserinfoRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + throw new ArgumentNullException(nameof(context)); + } - context.Subject = context.Principal.GetClaim(Claims.Subject); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // The following claims are all optional and should be excluded when - // no corresponding value has been found in the authentication principal: + context.Subject = context.Principal.GetClaim(Claims.Subject); - if (context.Principal.HasScope(Scopes.Profile)) - { - context.FamilyName = context.Principal.GetClaim(Claims.FamilyName); - context.GivenName = context.Principal.GetClaim(Claims.GivenName); - context.BirthDate = context.Principal.GetClaim(Claims.Birthdate); - } + // The following claims are all optional and should be excluded when + // no corresponding value has been found in the authentication principal: - if (context.Principal.HasScope(Scopes.Email)) - { - context.Email = context.Principal.GetClaim(Claims.Email); - } + if (context.Principal.HasScope(Scopes.Profile)) + { + context.FamilyName = context.Principal.GetClaim(Claims.FamilyName); + context.GivenName = context.Principal.GetClaim(Claims.GivenName); + context.BirthDate = context.Principal.GetClaim(Claims.Birthdate); + } - if (context.Principal.HasScope(Scopes.Phone)) - { - context.PhoneNumber = context.Principal.GetClaim(Claims.PhoneNumber); - } + if (context.Principal.HasScope(Scopes.Email)) + { + context.Email = context.Principal.GetClaim(Claims.Email); + } - return default; + if (context.Principal.HasScope(Scopes.Phone)) + { + context.PhoneNumber = context.Principal.GetClaim(Claims.PhoneNumber); } + + return default; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 5b4dbe6c..897af3cb 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -22,3117 +22,3116 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class OpenIddictServerHandlers { - [EditorBrowsable(EditorBrowsableState.Never)] - public static partial class OpenIddictServerHandlers + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Authentication processing: + */ + ValidateAuthenticationDemand.Descriptor, + EvaluateValidatedTokens.Descriptor, + ResolveValidatedTokens.Descriptor, + ValidateAccessToken.Descriptor, + ValidateAuthorizationCode.Descriptor, + ValidateDeviceCode.Descriptor, + ValidateGenericToken.Descriptor, + ValidateIdentityToken.Descriptor, + ValidateRefreshToken.Descriptor, + ValidateUserCode.Descriptor, + + /* + * Challenge processing: + */ + ValidateChallengeDemand.Descriptor, + AttachDefaultChallengeError.Descriptor, + RejectDeviceCodeEntry.Descriptor, + RejectUserCodeEntry.Descriptor, + AttachChallengeParameters.Descriptor, + + /* + * Sign-in processing: + */ + ValidateSignInDemand.Descriptor, + RestoreInternalClaims.Descriptor, + AttachDefaultScopes.Descriptor, + AttachDefaultPresenters.Descriptor, + InferResources.Descriptor, + EvaluateGeneratedTokens.Descriptor, + AttachAuthorization.Descriptor, + + PrepareAccessTokenPrincipal.Descriptor, + PrepareAuthorizationCodePrincipal.Descriptor, + PrepareDeviceCodePrincipal.Descriptor, + PrepareRefreshTokenPrincipal.Descriptor, + PrepareIdentityTokenPrincipal.Descriptor, + PrepareUserCodePrincipal.Descriptor, + + RedeemTokenEntry.Descriptor, + + GenerateAccessToken.Descriptor, + GenerateAuthorizationCode.Descriptor, + GenerateDeviceCode.Descriptor, + GenerateRefreshToken.Descriptor, + + AttachDeviceCodeIdentifier.Descriptor, + UpdateReferenceDeviceCodeEntry.Descriptor, + AttachTokenDigests.Descriptor, + + GenerateUserCode.Descriptor, + GenerateIdentityToken.Descriptor, + + AttachSignInParameters.Descriptor, + + /* + * Sign-out processing: + */ + ValidateSignOutDemand.Descriptor, + AttachSignOutParameters.Descriptor, + + /* + * Error processing: + */ + AttachErrorParameters.Descriptor) + + .AddRange(Authentication.DefaultHandlers) + .AddRange(Device.DefaultHandlers) + .AddRange(Discovery.DefaultHandlers) + .AddRange(Exchange.DefaultHandlers) + .AddRange(Introspection.DefaultHandlers) + .AddRange(Protection.DefaultHandlers) + .AddRange(Revocation.DefaultHandlers) + .AddRange(Session.DefaultHandlers) + .AddRange(Userinfo.DefaultHandlers); + + /// + /// Contains the logic responsible of rejecting authentication demands made from unsupported endpoints. + /// + public class ValidateAuthenticationDemand : IOpenIddictServerHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Authentication processing: - */ - ValidateAuthenticationDemand.Descriptor, - EvaluateValidatedTokens.Descriptor, - ResolveValidatedTokens.Descriptor, - ValidateAccessToken.Descriptor, - ValidateAuthorizationCode.Descriptor, - ValidateDeviceCode.Descriptor, - ValidateGenericToken.Descriptor, - ValidateIdentityToken.Descriptor, - ValidateRefreshToken.Descriptor, - ValidateUserCode.Descriptor, - - /* - * Challenge processing: - */ - ValidateChallengeDemand.Descriptor, - AttachDefaultChallengeError.Descriptor, - RejectDeviceCodeEntry.Descriptor, - RejectUserCodeEntry.Descriptor, - AttachChallengeParameters.Descriptor, - - /* - * Sign-in processing: - */ - ValidateSignInDemand.Descriptor, - RestoreInternalClaims.Descriptor, - AttachDefaultScopes.Descriptor, - AttachDefaultPresenters.Descriptor, - InferResources.Descriptor, - EvaluateGeneratedTokens.Descriptor, - AttachAuthorization.Descriptor, - - PrepareAccessTokenPrincipal.Descriptor, - PrepareAuthorizationCodePrincipal.Descriptor, - PrepareDeviceCodePrincipal.Descriptor, - PrepareRefreshTokenPrincipal.Descriptor, - PrepareIdentityTokenPrincipal.Descriptor, - PrepareUserCodePrincipal.Descriptor, - - RedeemTokenEntry.Descriptor, - - GenerateAccessToken.Descriptor, - GenerateAuthorizationCode.Descriptor, - GenerateDeviceCode.Descriptor, - GenerateRefreshToken.Descriptor, - - AttachDeviceCodeIdentifier.Descriptor, - UpdateReferenceDeviceCodeEntry.Descriptor, - AttachTokenDigests.Descriptor, - - GenerateUserCode.Descriptor, - GenerateIdentityToken.Descriptor, - - AttachSignInParameters.Descriptor, - - /* - * Sign-out processing: - */ - ValidateSignOutDemand.Descriptor, - AttachSignOutParameters.Descriptor, - - /* - * Error processing: - */ - AttachErrorParameters.Descriptor) - - .AddRange(Authentication.DefaultHandlers) - .AddRange(Device.DefaultHandlers) - .AddRange(Discovery.DefaultHandlers) - .AddRange(Exchange.DefaultHandlers) - .AddRange(Introspection.DefaultHandlers) - .AddRange(Protection.DefaultHandlers) - .AddRange(Revocation.DefaultHandlers) - .AddRange(Session.DefaultHandlers) - .AddRange(Userinfo.DefaultHandlers); - /// - /// Contains the logic responsible of rejecting authentication demands made from unsupported endpoints. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAuthenticationDemand : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - switch (context.EndpointType) - { - case OpenIddictServerEndpointType.Authorization: - case OpenIddictServerEndpointType.Introspection: - case OpenIddictServerEndpointType.Logout: - case OpenIddictServerEndpointType.Revocation: - case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType(): - case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType(): - case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType(): - case OpenIddictServerEndpointType.Userinfo: - case OpenIddictServerEndpointType.Verification: - return default; - - case OpenIddictServerEndpointType.Token: - throw new InvalidOperationException(SR.GetResourceString(SR.ID0001)); - - default: throw new InvalidOperationException(SR.GetResourceString(SR.ID0002)); - } + switch (context.EndpointType) + { + case OpenIddictServerEndpointType.Authorization: + case OpenIddictServerEndpointType.Introspection: + case OpenIddictServerEndpointType.Logout: + case OpenIddictServerEndpointType.Revocation: + case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType(): + case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType(): + case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType(): + case OpenIddictServerEndpointType.Userinfo: + case OpenIddictServerEndpointType.Verification: + return default; + + case OpenIddictServerEndpointType.Token: + throw new InvalidOperationException(SR.GetResourceString(SR.ID0001)); + + default: throw new InvalidOperationException(SR.GetResourceString(SR.ID0002)); } } + } + /// + /// Contains the logic responsible of selecting the token types that should be validated. + /// + public class EvaluateValidatedTokens : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of selecting the token types that should be validated. + /// Gets the default descriptor definition assigned to this handler. /// - public class EvaluateValidatedTokens : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - (context.ValidateAccessToken, context.RequireAccessToken) = context.EndpointType switch - { - // The userinfo endpoint requires sending a valid access token. - OpenIddictServerEndpointType.Userinfo => (true, true), + (context.ValidateAccessToken, context.RequireAccessToken) = context.EndpointType switch + { + // The userinfo endpoint requires sending a valid access token. + OpenIddictServerEndpointType.Userinfo => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - (context.ValidateAuthorizationCode, context.RequireAuthorizationCode) = context.EndpointType switch - { - // The authorization code grant requires sending a valid authorization code. - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() => (true, true), + (context.ValidateAuthorizationCode, context.RequireAuthorizationCode) = context.EndpointType switch + { + // The authorization code grant requires sending a valid authorization code. + OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - (context.ValidateDeviceCode, context.RequireDeviceCode) = context.EndpointType switch - { - // The device code grant requires sending a valid device code. - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() => (true, true), + (context.ValidateDeviceCode, context.RequireDeviceCode) = context.EndpointType switch + { + // The device code grant requires sending a valid device code. + OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - (context.ValidateGenericToken, context.RequireGenericToken) = context.EndpointType switch - { - // Tokens received by the introspection and revocation endpoints can be of any type. - // Additional token type filtering is made by the endpoint themselves, if needed. - OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation => (true, true), + (context.ValidateGenericToken, context.RequireGenericToken) = context.EndpointType switch + { + // Tokens received by the introspection and revocation endpoints can be of any type. + // Additional token type filtering is made by the endpoint themselves, if needed. + OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - (context.ValidateIdentityToken, context.RequireIdentityToken) = context.EndpointType switch - { - // The identity token received by the authorization and logout - // endpoints are not required and serve as optional hints. - OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout => (true, false), + (context.ValidateIdentityToken, context.RequireIdentityToken) = context.EndpointType switch + { + // The identity token received by the authorization and logout + // endpoints are not required and serve as optional hints. + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout => (true, false), - _ => (false, false) - }; + _ => (false, false) + }; - (context.ValidateRefreshToken, context.RequireRefreshToken) = context.EndpointType switch - { - // The refresh token grant requires sending a valid refresh token. - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() => (true, true), + (context.ValidateRefreshToken, context.RequireRefreshToken) = context.EndpointType switch + { + // The refresh token grant requires sending a valid refresh token. + OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - (context.ValidateUserCode, context.RequireUserCode) = context.EndpointType switch - { - // Note: the verification endpoint can be accessed without specifying a - // user code (that can be later set by the user using a form, for instance). - OpenIddictServerEndpointType.Verification => (true, false), + (context.ValidateUserCode, context.RequireUserCode) = context.EndpointType switch + { + // Note: the verification endpoint can be accessed without specifying a + // user code (that can be later set by the user using a form, for instance). + OpenIddictServerEndpointType.Verification => (true, false), - _ => (false, false) - }; + _ => (false, false) + }; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of resolving the token from the incoming request. + /// + public class ResolveValidatedTokens : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of resolving the token from the incoming request. + /// Gets the default descriptor definition assigned to this handler. /// - public class ResolveValidatedTokens : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.AccessToken = context.EndpointType switch - { - OpenIddictServerEndpointType.Userinfo when context.ValidateAccessToken => context.Request.AccessToken, + context.AccessToken = context.EndpointType switch + { + OpenIddictServerEndpointType.Userinfo when context.ValidateAccessToken => context.Request.AccessToken, - _ => null - }; + _ => null + }; - context.AuthorizationCode = context.EndpointType switch - { - OpenIddictServerEndpointType.Token when context.ValidateAuthorizationCode => context.Request.Code, + context.AuthorizationCode = context.EndpointType switch + { + OpenIddictServerEndpointType.Token when context.ValidateAuthorizationCode => context.Request.Code, - _ => null - }; + _ => null + }; - context.DeviceCode = context.EndpointType switch - { - OpenIddictServerEndpointType.Token when context.ValidateDeviceCode => context.Request.DeviceCode, + context.DeviceCode = context.EndpointType switch + { + OpenIddictServerEndpointType.Token when context.ValidateDeviceCode => context.Request.DeviceCode, - _ => null - }; + _ => null + }; - (context.GenericToken, context.GenericTokenTypeHint) = context.EndpointType switch - { - OpenIddictServerEndpointType.Introspection or - OpenIddictServerEndpointType.Revocation - when context.ValidateGenericToken => (context.Request.Token, context.Request.TokenTypeHint), + (context.GenericToken, context.GenericTokenTypeHint) = context.EndpointType switch + { + OpenIddictServerEndpointType.Introspection or + OpenIddictServerEndpointType.Revocation + when context.ValidateGenericToken => (context.Request.Token, context.Request.TokenTypeHint), - _ => (null, null) - }; + _ => (null, null) + }; - context.IdentityToken = context.EndpointType switch - { - OpenIddictServerEndpointType.Authorization or - OpenIddictServerEndpointType.Logout - when context.ValidateIdentityToken => context.Request.IdTokenHint, + context.IdentityToken = context.EndpointType switch + { + OpenIddictServerEndpointType.Authorization or + OpenIddictServerEndpointType.Logout + when context.ValidateIdentityToken => context.Request.IdTokenHint, - _ => null - }; + _ => null + }; - context.RefreshToken = context.EndpointType switch - { - OpenIddictServerEndpointType.Token when context.ValidateRefreshToken => context.Request.RefreshToken, + context.RefreshToken = context.EndpointType switch + { + OpenIddictServerEndpointType.Token when context.ValidateRefreshToken => context.Request.RefreshToken, - _ => null - }; + _ => null + }; - context.UserCode = context.EndpointType switch - { - OpenIddictServerEndpointType.Verification when context.ValidateUserCode => context.Request.UserCode, + context.UserCode = context.EndpointType switch + { + OpenIddictServerEndpointType.Verification when context.ValidateUserCode => context.Request.UserCode, - _ => null - }; + _ => null + }; - return default; - } + return default; } + } + + /// + /// Contains the logic responsible of validating the access token resolved from the context. + /// + public class ValidateAccessToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateAccessToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating the access token resolved from the context. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAccessToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ResolveValidatedTokens.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateAccessToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ResolveValidatedTokens.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.AccessTokenPrincipal is not null) - { - return; - } + if (context.AccessTokenPrincipal is not null) + { + return; + } - if (string.IsNullOrEmpty(context.AccessToken)) + if (string.IsNullOrEmpty(context.AccessToken)) + { + if (context.RequireAccessToken) { - if (context.RequireAccessToken) - { - context.Reject( - error: Errors.MissingToken, - description: SR.GetResourceString(SR.ID2000), - uri: SR.FormatID8000(SR.ID2000)); - - return; - } + context.Reject( + error: Errors.MissingToken, + description: SR.GetResourceString(SR.ID2000), + uri: SR.FormatID8000(SR.ID2000)); return; } - var notification = new ValidateTokenContext(context.Transaction) - { - Token = context.AccessToken, - ValidTokenTypes = { TokenTypeHints.AccessToken } - }; + return; + } - await _dispatcher.DispatchAsync(notification); + var notification = new ValidateTokenContext(context.Transaction) + { + Token = context.AccessToken, + ValidTokenTypes = { TokenTypeHints.AccessToken } + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.AccessTokenPrincipal = notification.Principal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.AccessTokenPrincipal = notification.Principal; } + } + + /// + /// Contains the logic responsible of validating the authorization code resolved from the context. + /// + public class ValidateAuthorizationCode : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateAuthorizationCode(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating the authorization code resolved from the context. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAuthorizationCode : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateAccessToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateAuthorizationCode(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateAccessToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.AuthorizationCodePrincipal is not null) - { - return; - } + if (context.AuthorizationCodePrincipal is not null) + { + return; + } - if (string.IsNullOrEmpty(context.AuthorizationCode)) + if (string.IsNullOrEmpty(context.AuthorizationCode)) + { + if (context.RequireAuthorizationCode) { - if (context.RequireAuthorizationCode) - { - context.Reject( - error: Errors.MissingToken, - description: SR.GetResourceString(SR.ID2000), - uri: SR.FormatID8000(SR.ID2000)); - - return; - } + context.Reject( + error: Errors.MissingToken, + description: SR.GetResourceString(SR.ID2000), + uri: SR.FormatID8000(SR.ID2000)); return; } - var notification = new ValidateTokenContext(context.Transaction) - { - Token = context.AuthorizationCode, - ValidTokenTypes = { TokenTypeHints.AuthorizationCode } - }; + return; + } - await _dispatcher.DispatchAsync(notification); + var notification = new ValidateTokenContext(context.Transaction) + { + Token = context.AuthorizationCode, + ValidTokenTypes = { TokenTypeHints.AuthorizationCode } + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.AuthorizationCodePrincipal = notification.Principal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.AuthorizationCodePrincipal = notification.Principal; } + } + + /// + /// Contains the logic responsible of validating the device code resolved from the context. + /// + public class ValidateDeviceCode : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateDeviceCode(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating the device code resolved from the context. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateDeviceCode : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateAuthorizationCode.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateDeviceCode(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateAuthorizationCode.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.DeviceCodePrincipal is not null) - { - return; - } + if (context.DeviceCodePrincipal is not null) + { + return; + } - if (string.IsNullOrEmpty(context.DeviceCode)) + if (string.IsNullOrEmpty(context.DeviceCode)) + { + if (context.RequireDeviceCode) { - if (context.RequireDeviceCode) - { - context.Reject( - error: Errors.MissingToken, - description: SR.GetResourceString(SR.ID2000), - uri: SR.FormatID8000(SR.ID2000)); - - return; - } + context.Reject( + error: Errors.MissingToken, + description: SR.GetResourceString(SR.ID2000), + uri: SR.FormatID8000(SR.ID2000)); return; } - var notification = new ValidateTokenContext(context.Transaction) - { - Token = context.DeviceCode, - ValidTokenTypes = { TokenTypeHints.DeviceCode } - }; + return; + } - await _dispatcher.DispatchAsync(notification); + var notification = new ValidateTokenContext(context.Transaction) + { + Token = context.DeviceCode, + ValidTokenTypes = { TokenTypeHints.DeviceCode } + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.DeviceCodePrincipal = notification.Principal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.DeviceCodePrincipal = notification.Principal; } + } + + /// + /// Contains the logic responsible of validating tokens of unknown types resolved from the context. + /// + public class ValidateGenericToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateGenericToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating tokens of unknown types resolved from the context. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateGenericToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateDeviceCode.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateGenericToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateDeviceCode.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.GenericTokenPrincipal is not null) - { - return; - } + if (context.GenericTokenPrincipal is not null) + { + return; + } - if (string.IsNullOrEmpty(context.GenericToken)) + if (string.IsNullOrEmpty(context.GenericToken)) + { + if (context.RequireGenericToken) { - if (context.RequireGenericToken) - { - context.Reject( - error: Errors.MissingToken, - description: SR.GetResourceString(SR.ID2000), - uri: SR.FormatID8000(SR.ID2000)); - - return; - } + context.Reject( + error: Errors.MissingToken, + description: SR.GetResourceString(SR.ID2000), + uri: SR.FormatID8000(SR.ID2000)); return; } - var notification = new ValidateTokenContext(context.Transaction) - { - Token = context.GenericToken, - TokenTypeHint = context.GenericTokenTypeHint, - - // By default, only access tokens and refresh tokens can be introspected/revoked but - // tokens received by the introspection and revocation endpoints can be of any type. - // - // Additional token type filtering is made by the endpoint themselves, if needed. - // As such, the valid token types list is deliberately left empty in this case. - ValidTokenTypes = { } - }; + return; + } + + var notification = new ValidateTokenContext(context.Transaction) + { + Token = context.GenericToken, + TokenTypeHint = context.GenericTokenTypeHint, - await _dispatcher.DispatchAsync(notification); + // By default, only access tokens and refresh tokens can be introspected/revoked but + // tokens received by the introspection and revocation endpoints can be of any type. + // + // Additional token type filtering is made by the endpoint themselves, if needed. + // As such, the valid token types list is deliberately left empty in this case. + ValidTokenTypes = { } + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.GenericTokenPrincipal = notification.Principal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.GenericTokenPrincipal = notification.Principal; } + } + + /// + /// Contains the logic responsible of validating the identity token resolved from the context. + /// + public class ValidateIdentityToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateIdentityToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating the identity token resolved from the context. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateIdentityToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateGenericToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateIdentityToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateGenericToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.IdentityTokenPrincipal is not null) - { - return; - } + if (context.IdentityTokenPrincipal is not null) + { + return; + } - if (string.IsNullOrEmpty(context.IdentityToken)) + if (string.IsNullOrEmpty(context.IdentityToken)) + { + if (context.RequireIdentityToken) { - if (context.RequireIdentityToken) - { - context.Reject( - error: Errors.MissingToken, - description: SR.GetResourceString(SR.ID2000), - uri: SR.FormatID8000(SR.ID2000)); - - return; - } + context.Reject( + error: Errors.MissingToken, + description: SR.GetResourceString(SR.ID2000), + uri: SR.FormatID8000(SR.ID2000)); return; } - var notification = new ValidateTokenContext(context.Transaction) - { - // Don't validate the lifetime of id_tokens used as id_token_hints. - DisableLifetimeValidation = context.EndpointType is OpenIddictServerEndpointType.Authorization or - OpenIddictServerEndpointType.Logout, - Token = context.IdentityToken, - ValidTokenTypes = { TokenTypeHints.IdToken } - }; + return; + } - await _dispatcher.DispatchAsync(notification); + var notification = new ValidateTokenContext(context.Transaction) + { + // Don't validate the lifetime of id_tokens used as id_token_hints. + DisableLifetimeValidation = context.EndpointType is OpenIddictServerEndpointType.Authorization or + OpenIddictServerEndpointType.Logout, + Token = context.IdentityToken, + ValidTokenTypes = { TokenTypeHints.IdToken } + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.IdentityTokenPrincipal = notification.Principal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.IdentityTokenPrincipal = notification.Principal; } + } + + /// + /// Contains the logic responsible of validating the refresh token resolved from the context. + /// + public class ValidateRefreshToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateRefreshToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating the refresh token resolved from the context. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateRefreshToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateIdentityToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateRefreshToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateIdentityToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.RefreshTokenPrincipal is not null) - { - return; - } + if (context.RefreshTokenPrincipal is not null) + { + return; + } - if (string.IsNullOrEmpty(context.RefreshToken)) + if (string.IsNullOrEmpty(context.RefreshToken)) + { + if (context.RequireRefreshToken) { - if (context.RequireRefreshToken) - { - context.Reject( - error: Errors.MissingToken, - description: SR.GetResourceString(SR.ID2000), - uri: SR.FormatID8000(SR.ID2000)); - - return; - } + context.Reject( + error: Errors.MissingToken, + description: SR.GetResourceString(SR.ID2000), + uri: SR.FormatID8000(SR.ID2000)); return; } - var notification = new ValidateTokenContext(context.Transaction) - { - Token = context.RefreshToken, - ValidTokenTypes = { TokenTypeHints.RefreshToken } - }; + return; + } - await _dispatcher.DispatchAsync(notification); + var notification = new ValidateTokenContext(context.Transaction) + { + Token = context.RefreshToken, + ValidTokenTypes = { TokenTypeHints.RefreshToken } + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.RefreshTokenPrincipal = notification.Principal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.RefreshTokenPrincipal = notification.Principal; } + } + + /// + /// Contains the logic responsible of validating the user code resolved from the context. + /// + public class ValidateUserCode : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public ValidateUserCode(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of validating the user code resolved from the context. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateUserCode : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateRefreshToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public ValidateUserCode(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateRefreshToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.UserCodePrincipal is not null) - { - return; - } + if (context.UserCodePrincipal is not null) + { + return; + } - if (string.IsNullOrEmpty(context.UserCode)) + if (string.IsNullOrEmpty(context.UserCode)) + { + if (context.RequireUserCode) { - if (context.RequireUserCode) - { - context.Reject( - error: Errors.MissingToken, - description: SR.GetResourceString(SR.ID2000), - uri: SR.FormatID8000(SR.ID2000)); - - return; - } + context.Reject( + error: Errors.MissingToken, + description: SR.GetResourceString(SR.ID2000), + uri: SR.FormatID8000(SR.ID2000)); return; } - var notification = new ValidateTokenContext(context.Transaction) - { - Token = context.UserCode, - ValidTokenTypes = { TokenTypeHints.UserCode } - }; + return; + } - await _dispatcher.DispatchAsync(notification); + var notification = new ValidateTokenContext(context.Transaction) + { + Token = context.UserCode, + ValidTokenTypes = { TokenTypeHints.UserCode } + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.UserCodePrincipal = notification.Principal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.UserCodePrincipal = notification.Principal; } + } + /// + /// Contains the logic responsible of rejecting challenge demands made from unsupported endpoints. + /// + public class ValidateChallengeDemand : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of rejecting challenge demands made from unsupported endpoints. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateChallengeDemand : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessChallengeContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or - OpenIddictServerEndpointType.Token or - OpenIddictServerEndpointType.Userinfo or - OpenIddictServerEndpointType.Verification)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - return default; + if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or + OpenIddictServerEndpointType.Token or + OpenIddictServerEndpointType.Userinfo or + OpenIddictServerEndpointType.Verification)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)); } + + return default; } + } + /// + /// Contains the logic responsible of ensuring that the challenge response contains an appropriate error. + /// + public class AttachDefaultChallengeError : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of ensuring that the challenge response contains an appropriate error. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachDefaultChallengeError : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateChallengeDemand.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateChallengeDemand.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessChallengeContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (!context.Parameters.ContainsKey(Parameters.Error)) + if (!context.Parameters.ContainsKey(Parameters.Error)) + { + context.Parameters[Parameters.Error] = context.EndpointType switch { - context.Parameters[Parameters.Error] = context.EndpointType switch - { - OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification - => Errors.AccessDenied, + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification + => Errors.AccessDenied, - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - OpenIddictServerEndpointType.Userinfo => Errors.InsufficientAccess, + OpenIddictServerEndpointType.Token => Errors.InvalidGrant, + OpenIddictServerEndpointType.Userinfo => Errors.InsufficientAccess, - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) - }; - } + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) + }; + } - if (!context.Parameters.ContainsKey(Parameters.ErrorDescription)) + if (!context.Parameters.ContainsKey(Parameters.ErrorDescription)) + { + context.Parameters[Parameters.ErrorDescription] = context.EndpointType switch { - context.Parameters[Parameters.ErrorDescription] = context.EndpointType switch - { - OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification - => SR.GetResourceString(SR.ID2015), + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification + => SR.GetResourceString(SR.ID2015), - OpenIddictServerEndpointType.Token => SR.GetResourceString(SR.ID2024), - OpenIddictServerEndpointType.Userinfo => SR.GetResourceString(SR.ID2025), + OpenIddictServerEndpointType.Token => SR.GetResourceString(SR.ID2024), + OpenIddictServerEndpointType.Userinfo => SR.GetResourceString(SR.ID2025), - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) - }; - } + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) + }; + } - if (!context.Parameters.ContainsKey(Parameters.ErrorUri)) + if (!context.Parameters.ContainsKey(Parameters.ErrorUri)) + { + context.Parameters[Parameters.ErrorUri] = context.EndpointType switch { - context.Parameters[Parameters.ErrorUri] = context.EndpointType switch - { - OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification - => SR.FormatID8000(SR.ID2015), - - OpenIddictServerEndpointType.Token => SR.FormatID8000(SR.ID2024), - OpenIddictServerEndpointType.Userinfo => SR.FormatID8000(SR.ID2025), + OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification + => SR.FormatID8000(SR.ID2015), - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) - }; - } + OpenIddictServerEndpointType.Token => SR.FormatID8000(SR.ID2024), + OpenIddictServerEndpointType.Userinfo => SR.FormatID8000(SR.ID2025), - return default; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) + }; } + + return default; } + } + + /// + /// Contains the logic responsible of rejecting the device code entry associated with the user code. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class RejectDeviceCodeEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictTokenManager _tokenManager; + + public RejectDeviceCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public RejectDeviceCodeEntry(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// - /// Contains the logic responsible of rejecting the device code entry associated with the user code. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class RejectDeviceCodeEntry : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(AttachDefaultChallengeError.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessChallengeContext context) { - private readonly IOpenIddictTokenManager _tokenManager; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public RejectDeviceCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + if (context.EndpointType != OpenIddictServerEndpointType.Verification) + { + return; + } - public RejectDeviceCodeEntry(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + var notification = context.Transaction.GetProperty( + typeof(ProcessAuthenticationContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(AttachDefaultChallengeError.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + Debug.Assert(notification.UserCodePrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - /// - public async ValueTask HandleAsync(ProcessChallengeContext context) + // Extract the device code identifier from the user code principal. + var identifier = notification.UserCodePrincipal.GetClaim(Claims.Private.DeviceCodeId); + if (string.IsNullOrEmpty(identifier)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.EndpointType != OpenIddictServerEndpointType.Verification) - { - return; - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0008)); + } - var notification = context.Transaction.GetProperty( - typeof(ProcessAuthenticationContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); + var token = await _tokenManager.FindByIdAsync(identifier); + if (token is not null) + { + await _tokenManager.TryRejectAsync(token); + } + } + } - Debug.Assert(notification.UserCodePrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + /// + /// Contains the logic responsible of rejecting the user code entry, if applicable. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class RejectUserCodeEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictTokenManager _tokenManager; - // Extract the device code identifier from the user code principal. - var identifier = notification.UserCodePrincipal.GetClaim(Claims.Private.DeviceCodeId); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0008)); - } + public RejectUserCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - var token = await _tokenManager.FindByIdAsync(identifier); - if (token is not null) - { - await _tokenManager.TryRejectAsync(token); - } - } - } + public RejectUserCodeEntry(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// - /// Contains the logic responsible of rejecting the user code entry, if applicable. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class RejectUserCodeEntry : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(RejectDeviceCodeEntry.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessChallengeContext context) { - private readonly IOpenIddictTokenManager _tokenManager; - - public RejectUserCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public RejectUserCodeEntry(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(RejectDeviceCodeEntry.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessChallengeContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - if (context.EndpointType != OpenIddictServerEndpointType.Verification) - { - return; - } + if (context.EndpointType != OpenIddictServerEndpointType.Verification) + { + return; + } - var notification = context.Transaction.GetProperty( - typeof(ProcessAuthenticationContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); + var notification = context.Transaction.GetProperty( + typeof(ProcessAuthenticationContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); - Debug.Assert(notification.UserCodePrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(notification.UserCodePrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // Extract the device code identifier from the authentication principal. - var identifier = notification.UserCodePrincipal.GetTokenId(); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0009)); - } + // Extract the device code identifier from the authentication principal. + var identifier = notification.UserCodePrincipal.GetTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0009)); + } - var token = await _tokenManager.FindByIdAsync(identifier); - if (token is not null) - { - await _tokenManager.TryRejectAsync(token); - } + var token = await _tokenManager.FindByIdAsync(identifier); + if (token is not null) + { + await _tokenManager.TryRejectAsync(token); } } + } + /// + /// Contains the logic responsible of attaching the appropriate parameters to the challenge response. + /// + public class AttachChallengeParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the appropriate parameters to the challenge response. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachChallengeParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(RejectUserCodeEntry.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(RejectUserCodeEntry.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessChallengeContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.Parameters.Count > 0) + if (context.Parameters.Count > 0) + { + foreach (var parameter in context.Parameters) { - foreach (var parameter in context.Parameters) - { - context.Response.SetParameter(parameter.Key, parameter.Value); - } + context.Response.SetParameter(parameter.Key, parameter.Value); } - - return default; } + + return default; } + } + /// + /// Contains the logic responsible of ensuring that the sign-in demand + /// is compatible with the type of the endpoint that handled the request. + /// + public class ValidateSignInDemand : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of ensuring that the sign-in demand - /// is compatible with the type of the endpoint that handled the request. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateSignInDemand : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or + OpenIddictServerEndpointType.Device or + OpenIddictServerEndpointType.Token or + OpenIddictServerEndpointType.Verification)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0010)); + } + + if (context.Principal is not { Identity: ClaimsIdentity }) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0011)); + } - if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or - OpenIddictServerEndpointType.Device or - OpenIddictServerEndpointType.Token or - OpenIddictServerEndpointType.Verification)) + // Note: sign-in operations triggered from the device endpoint can't be associated to specific users + // as users' identity is not known until they reach the verification endpoint and validate the user code. + // As such, the principal used in this case cannot contain an authenticated identity or a subject claim. + if (context.EndpointType == OpenIddictServerEndpointType.Device) + { + if (context.Principal.Identity.IsAuthenticated) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0010)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0012)); } - if (context.Principal is not { Identity: ClaimsIdentity }) + if (!string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject))) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0011)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0013)); } + } - // Note: sign-in operations triggered from the device endpoint can't be associated to specific users - // as users' identity is not known until they reach the verification endpoint and validate the user code. - // As such, the principal used in this case cannot contain an authenticated identity or a subject claim. - if (context.EndpointType == OpenIddictServerEndpointType.Device) + else + { + if (!context.Principal.Identity.IsAuthenticated) { - if (context.Principal.Identity.IsAuthenticated) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0012)); - } - - if (!string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0013)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0014)); } - else + if (string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject))) { - if (!context.Principal.Identity.IsAuthenticated) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0014)); - } - - if (string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0015)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0015)); } - - return default; } + + return default; } + } + /// + /// Contains the logic responsible of re-attaching internal claims to the authentication principal. + /// + public class RestoreInternalClaims : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of re-attaching internal claims to the authentication principal. + /// Gets the default descriptor definition assigned to this handler. /// - public class RestoreInternalClaims : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateSignInDemand.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateSignInDemand.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - switch (context.EndpointType) - { - case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType(): - case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType(): - case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType(): - case OpenIddictServerEndpointType.Verification: - break; + switch (context.EndpointType) + { + case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType(): + case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType(): + case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType(): + case OpenIddictServerEndpointType.Verification: + break; - default: return default; - } + default: return default; + } - var identity = (ClaimsIdentity) context.Principal.Identity; + var identity = (ClaimsIdentity) context.Principal.Identity; - var notification = context.Transaction.GetProperty( - typeof(ProcessAuthenticationContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); + var notification = context.Transaction.GetProperty( + typeof(ProcessAuthenticationContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); - var principal = context.EndpointType switch - { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => notification.AuthorizationCodePrincipal, + var principal = context.EndpointType switch + { + OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + => notification.AuthorizationCodePrincipal, - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => notification.DeviceCodePrincipal, + OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + => notification.DeviceCodePrincipal, - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => notification.RefreshTokenPrincipal, + OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + => notification.RefreshTokenPrincipal, - OpenIddictServerEndpointType.Verification => notification.UserCodePrincipal, + OpenIddictServerEndpointType.Verification => notification.UserCodePrincipal, - _ => null - }; + _ => null + }; + + if (principal is null) + { + return default; + } - if (principal is null) + // Restore the internal claims resolved from the token. + foreach (var claims in principal.Claims + .Where(claim => claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) + .GroupBy(claim => claim.Type)) + { + // If the specified principal already contains one claim of the iterated type, ignore them. + if (context.Principal.Claims.Any(claim => claim.Type == claims.Key)) { - return default; + continue; } - // Restore the internal claims resolved from the token. - foreach (var claims in principal.Claims - .Where(claim => claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) - .GroupBy(claim => claim.Type)) + // When the request is a verification request, don't flow the scopes from the user code. + if (context.EndpointType == OpenIddictServerEndpointType.Verification && + string.Equals(claims.Key, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase)) { - // If the specified principal already contains one claim of the iterated type, ignore them. - if (context.Principal.Claims.Any(claim => claim.Type == claims.Key)) - { - continue; - } - - // When the request is a verification request, don't flow the scopes from the user code. - if (context.EndpointType == OpenIddictServerEndpointType.Verification && - string.Equals(claims.Key, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - identity.AddClaims(claims); + continue; } - return default; + identity.AddClaims(claims); } + + return default; } + } + /// + /// Contains the logic responsible of attaching default scopes to the authentication principal. + /// + public class AttachDefaultScopes : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching default scopes to the authentication principal. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachDefaultScopes : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(RestoreInternalClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(RestoreInternalClaims.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Always include the "openid" scope when the developer doesn't explicitly call SetScopes. - // Note: the application is allowed to specify a different "scopes": in this case, - // don't replace the "scopes" property stored in the authentication ticket. - if (!context.Principal.HasClaim(Claims.Private.Scope) && context.Request.HasScope(Scopes.OpenId)) - { - context.Principal.SetScopes(Scopes.OpenId); - } + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - return default; + // Always include the "openid" scope when the developer doesn't explicitly call SetScopes. + // Note: the application is allowed to specify a different "scopes": in this case, + // don't replace the "scopes" property stored in the authentication ticket. + if (!context.Principal.HasClaim(Claims.Private.Scope) && context.Request.HasScope(Scopes.OpenId)) + { + context.Principal.SetScopes(Scopes.OpenId); } + + return default; } + } + /// + /// Contains the logic responsible of attaching default presenters to the authentication principal. + /// + public class AttachDefaultPresenters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching default presenters to the authentication principal. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachDefaultPresenters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachDefaultScopes.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachDefaultScopes.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Add the validated client_id to the list of authorized presenters, - // unless the presenters were explicitly set by the developer. - if (!context.Principal.HasClaim(Claims.Private.Presenter) && !string.IsNullOrEmpty(context.ClientId)) - { - context.Principal.SetPresenters(context.ClientId); - } + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - return default; + // Add the validated client_id to the list of authorized presenters, + // unless the presenters were explicitly set by the developer. + if (!context.Principal.HasClaim(Claims.Private.Presenter) && !string.IsNullOrEmpty(context.ClientId)) + { + context.Principal.SetPresenters(context.ClientId); } + + return default; } + } + /// + /// Contains the logic responsible of inferring resources from the audience claims if necessary. + /// + public class InferResources : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of inferring resources from the audience claims if necessary. + /// Gets the default descriptor definition assigned to this handler. /// - public class InferResources : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachDefaultPresenters.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachDefaultPresenters.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // When a "resources" property cannot be found in the ticket, infer it from the "audiences" property. - if (context.Principal.HasClaim(Claims.Private.Audience) && - !context.Principal.HasClaim(Claims.Private.Resource)) - { - context.Principal.SetResources(context.Principal.GetAudiences()); - } + // When a "resources" property cannot be found in the ticket, infer it from the "audiences" property. + if (context.Principal.HasClaim(Claims.Private.Audience) && + !context.Principal.HasClaim(Claims.Private.Resource)) + { + context.Principal.SetResources(context.Principal.GetAudiences()); + } - // Reset the audiences collection, as it's later set, based on the token type. - context.Principal.SetAudiences(ImmutableArray.Create()); + // Reset the audiences collection, as it's later set, based on the token type. + context.Principal.SetAudiences(ImmutableArray.Create()); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of selecting the token types that + /// should be generated and optionally returned in the response. + /// + public class EvaluateGeneratedTokens : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of selecting the token types that - /// should be generated and optionally returned in the response. + /// Gets the default descriptor definition assigned to this handler. /// - public class EvaluateGeneratedTokens : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(InferResources.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(InferResources.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - (context.GenerateAccessToken, context.IncludeAccessToken) = context.EndpointType switch - { - // For authorization requests, generate and return an access token - // if a response type containing the "token" value was specified. - OpenIddictServerEndpointType.Authorization when context.Request.HasResponseType(ResponseTypes.Token) - => (true, true), + (context.GenerateAccessToken, context.IncludeAccessToken) = context.EndpointType switch + { + // For authorization requests, generate and return an access token + // if a response type containing the "token" value was specified. + OpenIddictServerEndpointType.Authorization when context.Request.HasResponseType(ResponseTypes.Token) + => (true, true), - // For token requests, always generate and return an access token. - OpenIddictServerEndpointType.Token => (true, true), + // For token requests, always generate and return an access token. + OpenIddictServerEndpointType.Token => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - (context.GenerateAuthorizationCode, context.IncludeAuthorizationCode) = context.EndpointType switch - { - // For authorization requests, generate and return an authorization code - // if a response type containing the "code" value was specified. - OpenIddictServerEndpointType.Authorization when context.Request.HasResponseType(ResponseTypes.Code) - => (true, true), + (context.GenerateAuthorizationCode, context.IncludeAuthorizationCode) = context.EndpointType switch + { + // For authorization requests, generate and return an authorization code + // if a response type containing the "code" value was specified. + OpenIddictServerEndpointType.Authorization when context.Request.HasResponseType(ResponseTypes.Code) + => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - (context.GenerateDeviceCode, context.IncludeDeviceCode) = context.EndpointType switch - { - // For device requests, always generate and return a device code. - OpenIddictServerEndpointType.Device => (true, true), + (context.GenerateDeviceCode, context.IncludeDeviceCode) = context.EndpointType switch + { + // For device requests, always generate and return a device code. + OpenIddictServerEndpointType.Device => (true, true), - // Note: a device code is not directly returned by the verification endpoint (that generally - // returns an empty response or redirects the user agent to another page), but a device code - // must be generated to replace the payload of the device code initially returned to the client. - // In this case, the device code is not returned as part of the response but persisted in the DB. - OpenIddictServerEndpointType.Verification => (true, false), + // Note: a device code is not directly returned by the verification endpoint (that generally + // returns an empty response or redirects the user agent to another page), but a device code + // must be generated to replace the payload of the device code initially returned to the client. + // In this case, the device code is not returned as part of the response but persisted in the DB. + OpenIddictServerEndpointType.Verification => (true, false), - _ => (false, false) - }; + _ => (false, false) + }; - (context.GenerateIdentityToken, context.IncludeIdentityToken) = context.EndpointType switch - { - // For authorization requests, generate and return an identity token if a response type - // containing code was specified and if the openid scope was explicitly or implicitly granted. - OpenIddictServerEndpointType.Authorization when - context.Principal.HasScope(Scopes.OpenId) && - context.Request.HasResponseType(ResponseTypes.IdToken) => (true, true), + (context.GenerateIdentityToken, context.IncludeIdentityToken) = context.EndpointType switch + { + // For authorization requests, generate and return an identity token if a response type + // containing code was specified and if the openid scope was explicitly or implicitly granted. + OpenIddictServerEndpointType.Authorization when + context.Principal.HasScope(Scopes.OpenId) && + context.Request.HasResponseType(ResponseTypes.IdToken) => (true, true), - // For token requests, only generate and return an identity token if the openid scope was granted. - OpenIddictServerEndpointType.Token when context.Principal.HasScope(Scopes.OpenId) => (true, true), + // For token requests, only generate and return an identity token if the openid scope was granted. + OpenIddictServerEndpointType.Token when context.Principal.HasScope(Scopes.OpenId) => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - (context.GenerateRefreshToken, context.IncludeRefreshToken) = context.EndpointType switch - { - // For token requests, allow a refresh token to be returned - // if the special offline_access protocol scope was granted. - OpenIddictServerEndpointType.Token when context.Principal.HasScope(Scopes.OfflineAccess) - => (true, true), + (context.GenerateRefreshToken, context.IncludeRefreshToken) = context.EndpointType switch + { + // For token requests, allow a refresh token to be returned + // if the special offline_access protocol scope was granted. + OpenIddictServerEndpointType.Token when context.Principal.HasScope(Scopes.OfflineAccess) + => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - (context.GenerateUserCode, context.IncludeUserCode) = context.EndpointType switch - { - // Only generate and return a user code if the request is a device authorization request. - OpenIddictServerEndpointType.Device => (true, true), + (context.GenerateUserCode, context.IncludeUserCode) = context.EndpointType switch + { + // Only generate and return a user code if the request is a device authorization request. + OpenIddictServerEndpointType.Device => (true, true), - _ => (false, false) - }; + _ => (false, false) + }; - return default; - } + return default; + } + } + + /// + /// Contains the logic responsible of creating an ad-hoc authorization, if necessary. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class AttachAuthorization : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictAuthorizationManager _authorizationManager; + + public AttachAuthorization() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + + public AttachAuthorization( + IOpenIddictApplicationManager applicationManager, + IOpenIddictAuthorizationManager authorizationManager) + { + _applicationManager = applicationManager; + _authorizationManager = authorizationManager; } /// - /// Contains the logic responsible of creating an ad-hoc authorization, if necessary. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachAuthorization : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessSignInContext context) { - private readonly IOpenIddictApplicationManager _applicationManager; - private readonly IOpenIddictAuthorizationManager _authorizationManager; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public AttachAuthorization() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - public AttachAuthorization( - IOpenIddictApplicationManager applicationManager, - IOpenIddictAuthorizationManager authorizationManager) + // If no authorization code, device code or refresh token is returned, don't create an authorization. + if (!context.GenerateAuthorizationCode && !context.GenerateDeviceCode && !context.GenerateRefreshToken) { - _applicationManager = applicationManager; - _authorizationManager = authorizationManager; + return; } - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + // If an authorization identifier was explicitly specified, don't create an ad-hoc authorization. + if (!string.IsNullOrEmpty(context.Principal.GetAuthorizationId())) + { + return; + } - /// - public async ValueTask HandleAsync(ProcessSignInContext context) + var descriptor = new OpenIddictAuthorizationDescriptor { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + CreationDate = DateTimeOffset.UtcNow, + Principal = context.Principal, + Status = Statuses.Valid, + Subject = context.Principal.GetClaim(Claims.Subject), + Type = AuthorizationTypes.AdHoc + }; - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + descriptor.Scopes.UnionWith(context.Principal.GetScopes()); - // If no authorization code, device code or refresh token is returned, don't create an authorization. - if (!context.GenerateAuthorizationCode && !context.GenerateDeviceCode && !context.GenerateRefreshToken) + // If the client application is known, associate it to the authorization. + if (!string.IsNullOrEmpty(context.Request.ClientId)) + { + var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); + if (application is null) { - return; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0017)); } - // If an authorization identifier was explicitly specified, don't create an ad-hoc authorization. - if (!string.IsNullOrEmpty(context.Principal.GetAuthorizationId())) - { - return; - } + descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); + } - var descriptor = new OpenIddictAuthorizationDescriptor - { - CreationDate = DateTimeOffset.UtcNow, - Principal = context.Principal, - Status = Statuses.Valid, - Subject = context.Principal.GetClaim(Claims.Subject), - Type = AuthorizationTypes.AdHoc - }; + var authorization = await _authorizationManager.CreateAsync(descriptor); + if (authorization is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0018)); + } + + var identifier = await _authorizationManager.GetIdAsync(authorization); + + if (string.IsNullOrEmpty(context.Request.ClientId)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6007), identifier); + } + + else + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6008), context.Request.ClientId, identifier); + } + + // Attach the unique identifier of the ad hoc authorization to the authentication principal + // so that it is attached to all the derived tokens, allowing batched revocations support. + context.Principal.SetAuthorizationId(identifier); + } + } + + /// + /// Contains the logic responsible of preparing and attaching the claims principal + /// used to generate the access token, if one is going to be returned. + /// + public class PrepareAccessTokenPrincipal : IOpenIddictServerHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(AttachAuthorization.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - descriptor.Scopes.UnionWith(context.Principal.GetScopes()); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // If the client application is known, associate it to the authorization. - if (!string.IsNullOrEmpty(context.Request.ClientId)) + // Create a new principal containing only the filtered claims. + // Actors identities are also filtered (delegation scenarios). + var principal = context.Principal.Clone(claim => + { + // Never exclude the subject and authorization identifier claims. + if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase)) { - var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); - if (application is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0017)); - } + return true; + } - descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); + // Never exclude the presenters and scope private claims. + if (string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase)) + { + return true; } - var authorization = await _authorizationManager.CreateAsync(descriptor); - if (authorization is null) + // Never include the public or internal token identifiers to ensure the identifiers + // that are automatically inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0018)); + return false; } - var identifier = await _authorizationManager.GetIdAsync(authorization); + // Never include the creation and expiration dates that are automatically + // inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) + { + return false; + } - if (string.IsNullOrEmpty(context.Request.ClientId)) + // Always exclude private claims, whose values must generally be kept secret. + if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6007), identifier); + return false; } - else + // Claims whose destination is not explicitly referenced or doesn't + // contain "access_token" are not included in the access token. + if (!claim.HasDestination(Destinations.AccessToken)) { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6008), context.Request.ClientId, identifier); + context.Logger.LogDebug(SR.GetResourceString(SR.ID6009), claim.Type); + + return false; } - // Attach the unique identifier of the ad hoc authorization to the authentication principal - // so that it is attached to all the derived tokens, allowing batched revocations support. - context.Principal.SetAuthorizationId(identifier); + return true; + }); + + // Remove the destinations from the claim properties. + foreach (var claim in principal.Claims) + { + claim.Properties.Remove(Properties.Destinations); } - } - /// - /// Contains the logic responsible of preparing and attaching the claims principal - /// used to generate the access token, if one is going to be returned. - /// - public class PrepareAccessTokenPrincipal : IOpenIddictServerHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachAuthorization.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + principal.SetCreationDate(DateTimeOffset.UtcNow); - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + var lifetime = context.Principal.GetAccessTokenLifetime() ?? context.Options.AccessTokenLifetime; + if (lifetime.HasValue) + { + principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); + } - // Create a new principal containing only the filtered claims. - // Actors identities are also filtered (delegation scenarios). - var principal = context.Principal.Clone(claim => - { - // Never exclude the subject and authorization identifier claims. - if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase)) - { - return true; - } + // Set the audiences based on the resource claims stored in the principal. + principal.SetAudiences(context.Principal.GetResources()); - // Never exclude the presenters and scope private claims. - if (string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase)) - { - return true; - } + // Store the client identifier in the public client_id claim, if available. + // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. + principal.SetClaim(Claims.ClientId, context.ClientId); - // Never include the public or internal token identifiers to ensure the identifiers - // that are automatically inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) - { - return false; - } + // When receiving a grant_type=refresh_token request, determine whether the client application + // requests a limited set of scopes and immediately replace the scopes collection if necessary. + if (context.EndpointType == OpenIddictServerEndpointType.Token && + context.Request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(context.Request.Scope)) + { + var scopes = context.Request.GetScopes(); + principal.SetScopes(scopes.Intersect(context.Principal.GetScopes())); - // Never include the creation and expiration dates that are automatically - // inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) - { - return false; - } + context.Logger.LogDebug(SR.GetResourceString(SR.ID6010), scopes); + } - // Always exclude private claims, whose values must generally be kept secret. - if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) - { - return false; - } + context.AccessTokenPrincipal = principal; - // Claims whose destination is not explicitly referenced or doesn't - // contain "access_token" are not included in the access token. - if (!claim.HasDestination(Destinations.AccessToken)) - { - context.Logger.LogDebug(SR.GetResourceString(SR.ID6009), claim.Type); + return default; + } + } - return false; - } + /// + /// Contains the logic responsible of preparing and attaching the claims principal + /// used to generate the authorization code, if one is going to be returned. + /// + public class PrepareAuthorizationCodePrincipal : IOpenIddictServerHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(PrepareAccessTokenPrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - return true; - }); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // Remove the destinations from the claim properties. - foreach (var claim in principal.Claims) + // Create a new principal containing only the filtered claims. + // Actors identities are also filtered (delegation scenarios). + var principal = context.Principal.Clone(claim => + { + // Never include the public or internal token identifiers to ensure the identifiers + // that are automatically inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { - claim.Properties.Remove(Properties.Destinations); + return false; } - principal.SetCreationDate(DateTimeOffset.UtcNow); - - var lifetime = context.Principal.GetAccessTokenLifetime() ?? context.Options.AccessTokenLifetime; - if (lifetime.HasValue) + // Never include the creation and expiration dates that are automatically + // inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { - principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); + return false; } - // Set the audiences based on the resource claims stored in the principal. - principal.SetAudiences(context.Principal.GetResources()); + // Other claims are always included in the authorization code, even private claims. + return true; + }); - // Store the client identifier in the public client_id claim, if available. - // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. - principal.SetClaim(Claims.ClientId, context.ClientId); + principal.SetCreationDate(DateTimeOffset.UtcNow); - // When receiving a grant_type=refresh_token request, determine whether the client application - // requests a limited set of scopes and immediately replace the scopes collection if necessary. - if (context.EndpointType == OpenIddictServerEndpointType.Token && - context.Request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(context.Request.Scope)) - { - var scopes = context.Request.GetScopes(); - principal.SetScopes(scopes.Intersect(context.Principal.GetScopes())); + var lifetime = context.Principal.GetAuthorizationCodeLifetime() ?? context.Options.AuthorizationCodeLifetime; + if (lifetime.HasValue) + { + principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); + } - context.Logger.LogDebug(SR.GetResourceString(SR.ID6010), scopes); - } + // Attach the redirect_uri to allow for later comparison when + // receiving a grant_type=authorization_code token request. + principal.SetClaim(Claims.Private.RedirectUri, context.Request.RedirectUri); - context.AccessTokenPrincipal = principal; + // Attach the code challenge and the code challenge methods to allow the ValidateCodeVerifier + // handler to validate the code verifier sent by the client as part of the token request. + if (!string.IsNullOrEmpty(context.Request.CodeChallenge)) + { + principal.SetClaim(Claims.Private.CodeChallenge, context.Request.CodeChallenge); - return default; + // Default to plain if no explicit code challenge method was specified. + principal.SetClaim(Claims.Private.CodeChallengeMethod, + !string.IsNullOrEmpty(context.Request.CodeChallengeMethod) ? + context.Request.CodeChallengeMethod : CodeChallengeMethods.Plain); } + + // Attach the nonce so that it can be later returned by + // the token endpoint as part of the JWT identity token. + principal.SetClaim(Claims.Private.Nonce, context.Request.Nonce); + + context.AuthorizationCodePrincipal = principal; + + return default; } + } + /// + /// Contains the logic responsible of preparing and attaching the claims principal + /// used to generate the device code, if one is going to be returned. + /// + public class PrepareDeviceCodePrincipal : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of preparing and attaching the claims principal - /// used to generate the authorization code, if one is going to be returned. + /// Gets the default descriptor definition assigned to this handler. /// - public class PrepareAuthorizationCodePrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(PrepareAuthorizationCodePrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(PrepareAccessTokenPrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // Create a new principal containing only the filtered claims. - // Actors identities are also filtered (delegation scenarios). - var principal = context.Principal.Clone(claim => + // Create a new principal containing only the filtered claims. + // Actors identities are also filtered (delegation scenarios). + var principal = context.Principal.Clone(claim => + { + // Never include the public or internal token identifiers to ensure the identifiers + // that are automatically inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { - // Never include the public or internal token identifiers to ensure the identifiers - // that are automatically inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Never include the creation and expiration dates that are automatically - // inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Other claims are always included in the authorization code, even private claims. - return true; - }); - - principal.SetCreationDate(DateTimeOffset.UtcNow); + return false; + } - var lifetime = context.Principal.GetAuthorizationCodeLifetime() ?? context.Options.AuthorizationCodeLifetime; - if (lifetime.HasValue) + // Never include the creation and expiration dates that are automatically + // inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { - principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); + return false; } - // Attach the redirect_uri to allow for later comparison when - // receiving a grant_type=authorization_code token request. - principal.SetClaim(Claims.Private.RedirectUri, context.Request.RedirectUri); + // Other claims are always included in the device code, even private claims. + return true; + }); - // Attach the code challenge and the code challenge methods to allow the ValidateCodeVerifier - // handler to validate the code verifier sent by the client as part of the token request. - if (!string.IsNullOrEmpty(context.Request.CodeChallenge)) - { - principal.SetClaim(Claims.Private.CodeChallenge, context.Request.CodeChallenge); + principal.SetCreationDate(DateTimeOffset.UtcNow); - // Default to plain if no explicit code challenge method was specified. - principal.SetClaim(Claims.Private.CodeChallengeMethod, - !string.IsNullOrEmpty(context.Request.CodeChallengeMethod) ? - context.Request.CodeChallengeMethod : CodeChallengeMethods.Plain); - } + var lifetime = context.Principal.GetDeviceCodeLifetime() ?? context.Options.DeviceCodeLifetime; + if (lifetime.HasValue) + { + principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); + } - // Attach the nonce so that it can be later returned by - // the token endpoint as part of the JWT identity token. - principal.SetClaim(Claims.Private.Nonce, context.Request.Nonce); + // Restore the device code internal token identifier from the principal + // resolved from the user code used in the user code verification request. + if (context.EndpointType == OpenIddictServerEndpointType.Verification) + { + principal.SetClaim(Claims.Private.TokenId, context.Principal.GetClaim(Claims.Private.DeviceCodeId)); + } - context.AuthorizationCodePrincipal = principal; + context.DeviceCodePrincipal = principal; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of preparing and attaching the claims principal + /// used to generate the refresh token, if one is going to be returned. + /// + public class PrepareRefreshTokenPrincipal : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of preparing and attaching the claims principal - /// used to generate the device code, if one is going to be returned. + /// Gets the default descriptor definition assigned to this handler. /// - public class PrepareDeviceCodePrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(PrepareDeviceCodePrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(PrepareAuthorizationCodePrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + + // Create a new principal containing only the filtered claims. + // Actors identities are also filtered (delegation scenarios). + var principal = context.Principal.Clone(claim => + { + // Never include the public or internal token identifiers to ensure the identifiers + // that are automatically inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentNullException(nameof(context)); + return false; } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - - // Create a new principal containing only the filtered claims. - // Actors identities are also filtered (delegation scenarios). - var principal = context.Principal.Clone(claim => + // Never include the creation and expiration dates that are automatically + // inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { - // Never include the public or internal token identifiers to ensure the identifiers - // that are automatically inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) - { - return false; - } + return false; + } - // Never include the creation and expiration dates that are automatically - // inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) - { - return false; - } + // Other claims are always included in the refresh token, even private claims. + return true; + }); - // Other claims are always included in the device code, even private claims. - return true; - }); + principal.SetCreationDate(DateTimeOffset.UtcNow); + + // When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed + // and must exactly match the expiration date of the refresh token used in the token request. + if (context.EndpointType == OpenIddictServerEndpointType.Token && + context.Request.IsRefreshTokenGrantType() && + context.Options.DisableSlidingRefreshTokenExpiration) + { + var notification = context.Transaction.GetProperty( + typeof(ProcessAuthenticationContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); + + Debug.Assert(notification.RefreshTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - principal.SetCreationDate(DateTimeOffset.UtcNow); + principal.SetExpirationDate(notification.RefreshTokenPrincipal.GetExpirationDate()); + } - var lifetime = context.Principal.GetDeviceCodeLifetime() ?? context.Options.DeviceCodeLifetime; + else + { + var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime; if (lifetime.HasValue) { principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); } + } - // Restore the device code internal token identifier from the principal - // resolved from the user code used in the user code verification request. - if (context.EndpointType == OpenIddictServerEndpointType.Verification) - { - principal.SetClaim(Claims.Private.TokenId, context.Principal.GetClaim(Claims.Private.DeviceCodeId)); - } - - context.DeviceCodePrincipal = principal; + context.RefreshTokenPrincipal = principal; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of preparing and attaching the claims principal + /// used to generate the identity token, if one is going to be returned. + /// + public class PrepareIdentityTokenPrincipal : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of preparing and attaching the claims principal - /// used to generate the refresh token, if one is going to be returned. + /// Gets the default descriptor definition assigned to this handler. /// - public class PrepareRefreshTokenPrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(PrepareRefreshTokenPrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(PrepareDeviceCodePrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // Create a new principal containing only the filtered claims. - // Actors identities are also filtered (delegation scenarios). - var principal = context.Principal.Clone(claim => + // Replace the principal by a new one containing only the filtered claims. + // Actors identities are also filtered (delegation scenarios). + var principal = context.Principal.Clone(claim => + { + // Never exclude the subject and authorization identifier claims. + if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase)) { - // Never include the public or internal token identifiers to ensure the identifiers - // that are automatically inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Never include the creation and expiration dates that are automatically - // inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Other claims are always included in the refresh token, even private claims. return true; - }); - - principal.SetCreationDate(DateTimeOffset.UtcNow); + } - // When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed - // and must exactly match the expiration date of the refresh token used in the token request. - if (context.EndpointType == OpenIddictServerEndpointType.Token && - context.Request.IsRefreshTokenGrantType() && - context.Options.DisableSlidingRefreshTokenExpiration) + // Never include the public or internal token identifiers to ensure the identifiers + // that are automatically inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { - var notification = context.Transaction.GetProperty( - typeof(ProcessAuthenticationContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); + return false; + } - Debug.Assert(notification.RefreshTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + // Never include the creation and expiration dates that are automatically + // inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) + { + return false; + } - principal.SetExpirationDate(notification.RefreshTokenPrincipal.GetExpirationDate()); + // Always exclude private claims by default, whose values must generally be kept secret. + if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) + { + return false; } - else + // Claims whose destination is not explicitly referenced or doesn't + // contain "id_token" are not included in the identity token. + if (!claim.HasDestination(Destinations.IdentityToken)) { - var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime; - if (lifetime.HasValue) - { - principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); - } + context.Logger.LogDebug(SR.GetResourceString(SR.ID6011), claim.Type); + + return false; } - context.RefreshTokenPrincipal = principal; + return true; + }); - return default; + // Remove the destinations from the claim properties. + foreach (var claim in principal.Claims) + { + claim.Properties.Remove(Properties.Destinations); } + + principal.SetCreationDate(DateTimeOffset.UtcNow); + + var lifetime = context.Principal.GetIdentityTokenLifetime() ?? context.Options.IdentityTokenLifetime; + if (lifetime.HasValue) + { + principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); + } + + if (!string.IsNullOrEmpty(context.ClientId)) + { + principal.SetAudiences(context.ClientId); + } + + // Use the client_id as the authorized party, if available. + // See https://openid.net/specs/openid-connect-core-1_0.html#IDToken for more information. + principal.SetClaim(Claims.AuthorizedParty, context.ClientId); + + // If a nonce was present in the authorization request, it MUST be included in the id_token generated + // by the token endpoint. For that, OpenIddict simply flows the nonce as an authorization code claim. + // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation for more information. + principal.SetClaim(Claims.Nonce, context.EndpointType switch + { + OpenIddictServerEndpointType.Authorization => context.Request.Nonce, + OpenIddictServerEndpointType.Token => context.Principal.GetClaim(Claims.Private.Nonce), + + _ => null + }); + + context.IdentityTokenPrincipal = principal; + + return default; } + } + /// + /// Contains the logic responsible of preparing and attaching the claims principal + /// used to generate the user code, if one is going to be returned. + /// + public class PrepareUserCodePrincipal : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of preparing and attaching the claims principal - /// used to generate the identity token, if one is going to be returned. + /// Gets the default descriptor definition assigned to this handler. /// - public class PrepareIdentityTokenPrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(PrepareIdentityTokenPrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(PrepareRefreshTokenPrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + + // Create a new principal containing only the filtered claims. + // Actors identities are also filtered (delegation scenarios). + var principal = context.Principal.Clone(claim => + { + // Never include the public or internal token identifiers to ensure the identifiers + // that are automatically inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentNullException(nameof(context)); + return false; } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - - // Replace the principal by a new one containing only the filtered claims. - // Actors identities are also filtered (delegation scenarios). - var principal = context.Principal.Clone(claim => + // Never include the creation and expiration dates that are automatically + // inherited from the parent token are not reused for the new token. + if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { - // Never exclude the subject and authorization identifier claims. - if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase)) - { - return true; - } + return false; + } - // Never include the public or internal token identifiers to ensure the identifiers - // that are automatically inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) - { - return false; - } + // Other claims are always included in the authorization code, even private claims. + return true; + }); - // Never include the creation and expiration dates that are automatically - // inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) - { - return false; - } + principal.SetCreationDate(DateTimeOffset.UtcNow); - // Always exclude private claims by default, whose values must generally be kept secret. - if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) - { - return false; - } + var lifetime = context.Principal.GetUserCodeLifetime() ?? context.Options.UserCodeLifetime; + if (lifetime.HasValue) + { + principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); + } - // Claims whose destination is not explicitly referenced or doesn't - // contain "id_token" are not included in the identity token. - if (!claim.HasDestination(Destinations.IdentityToken)) - { - context.Logger.LogDebug(SR.GetResourceString(SR.ID6011), claim.Type); + // Store the client_id as a public client_id claim. + principal.SetClaim(Claims.ClientId, context.Request.ClientId); - return false; - } + context.UserCodePrincipal = principal; - return true; - }); + return default; + } + } - // Remove the destinations from the claim properties. - foreach (var claim in principal.Claims) - { - claim.Properties.Remove(Properties.Destinations); - } + /// + /// Contains the logic responsible of redeeming the token entry corresponding to + /// the received authorization code, device code, user code or refresh token. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class RedeemTokenEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictTokenManager _tokenManager; - principal.SetCreationDate(DateTimeOffset.UtcNow); + public RedeemTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - var lifetime = context.Principal.GetIdentityTokenLifetime() ?? context.Options.IdentityTokenLifetime; - if (lifetime.HasValue) - { - principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); - } + public RedeemTokenEntry(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; - if (!string.IsNullOrEmpty(context.ClientId)) - { - principal.SetAudiences(context.ClientId); - } - - // Use the client_id as the authorized party, if available. - // See https://openid.net/specs/openid-connect-core-1_0.html#IDToken for more information. - principal.SetClaim(Claims.AuthorizedParty, context.ClientId); + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessSignInContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If a nonce was present in the authorization request, it MUST be included in the id_token generated - // by the token endpoint. For that, OpenIddict simply flows the nonce as an authorization code claim. - // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation for more information. - principal.SetClaim(Claims.Nonce, context.EndpointType switch - { - OpenIddictServerEndpointType.Authorization => context.Request.Nonce, - OpenIddictServerEndpointType.Token => context.Principal.GetClaim(Claims.Private.Nonce), + switch (context.EndpointType) + { + case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType(): + case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType(): + case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() && + !context.Options.DisableRollingRefreshTokens: + case OpenIddictServerEndpointType.Verification: + break; + + default: return; + } - _ => null - }); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.IdentityTokenPrincipal = principal; + // Extract the token identifier from the authentication principal. + // If no token identifier can be found, this indicates that the token has no backing database entry. + var identifier = context.Principal.GetTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + return; + } - return default; + // If rolling tokens are enabled or if the request is a a code or device code token request + // or a user code verification request, mark the token as redeemed to prevent future reuses. + var token = await _tokenManager.FindByIdAsync(identifier); + if (token is not null) + { + await _tokenManager.TryRedeemAsync(token); } } + } + + /// + /// Contains the logic responsible of generating an access token for the current sign-in operation. + /// + public class GenerateAccessToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public GenerateAccessToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of preparing and attaching the claims principal - /// used to generate the user code, if one is going to be returned. + /// Gets the default descriptor definition assigned to this handler. /// - public class PrepareUserCodePrincipal : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(RedeemTokenEntry.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(PrepareIdentityTokenPrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - - // Create a new principal containing only the filtered claims. - // Actors identities are also filtered (delegation scenarios). - var principal = context.Principal.Clone(claim => - { - // Never include the public or internal token identifiers to ensure the identifiers - // that are automatically inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Never include the creation and expiration dates that are automatically - // inherited from the parent token are not reused for the new token. - if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || - string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) - { - return false; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Other claims are always included in the authorization code, even private claims. - return true; - }); + var notification = new GenerateTokenContext(context.Transaction) + { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + // Access tokens can be converted to reference tokens if the + // corresponding option was enabled in the server options. + PersistTokenPayload = context.Options.UseReferenceAccessTokens, + Principal = context.AccessTokenPrincipal!, + TokenType = TokenTypeHints.AccessToken + }; + + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - principal.SetCreationDate(DateTimeOffset.UtcNow); + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - var lifetime = context.Principal.GetUserCodeLifetime() ?? context.Options.UserCodeLifetime; - if (lifetime.HasValue) - { - principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - // Store the client_id as a public client_id claim. - principal.SetClaim(Claims.ClientId, context.Request.ClientId); + context.AccessToken = notification.Token; + } + } - context.UserCodePrincipal = principal; + /// + /// Contains the logic responsible of generating an authorization code for the current sign-in operation. + /// + public class GenerateAuthorizationCode : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; - return default; - } - } + public GenerateAuthorizationCode(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of redeeming the token entry corresponding to - /// the received authorization code, device code, user code or refresh token. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class RedeemTokenEntry : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(GenerateAccessToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessSignInContext context) { - private readonly IOpenIddictTokenManager _tokenManager; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public RedeemTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); + var notification = new GenerateTokenContext(context.Transaction) + { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + PersistTokenPayload = !context.Options.DisableTokenStorage, + Principal = context.AuthorizationCodePrincipal!, + TokenType = TokenTypeHints.AuthorizationCode + }; - public RedeemTokenEntry(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + await _dispatcher.DispatchAsync(notification); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - /// - public async ValueTask HandleAsync(ProcessSignInContext context) + else if (notification.IsRequestSkipped) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + context.SkipRequest(); + return; + } - switch (context.EndpointType) - { - case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType(): - case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType(): - case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() && - !context.Options.DisableRollingRefreshTokens: - case OpenIddictServerEndpointType.Verification: - break; - - default: return; - } + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + context.AuthorizationCode = notification.Token; + } + } - // Extract the token identifier from the authentication principal. - // If no token identifier can be found, this indicates that the token has no backing database entry. - var identifier = context.Principal.GetTokenId(); - if (string.IsNullOrEmpty(identifier)) - { - return; - } + /// + /// Contains the logic responsible of generating a device code for the current sign-in operation. + /// + public class GenerateDeviceCode : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; - // If rolling tokens are enabled or if the request is a a code or device code token request - // or a user code verification request, mark the token as redeemed to prevent future reuses. - var token = await _tokenManager.FindByIdAsync(identifier); - if (token is not null) - { - await _tokenManager.TryRedeemAsync(token); - } - } - } + public GenerateDeviceCode(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of generating an access token for the current sign-in operation. + /// Gets the default descriptor definition assigned to this handler. /// - public class GenerateAccessToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(GenerateAuthorizationCode.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessSignInContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public GenerateAccessToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(RedeemTokenEntry.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var notification = new GenerateTokenContext(context.Transaction) + var notification = new GenerateTokenContext(context.Transaction) + { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + // Device codes can be converted to reference tokens if they are not generated + // as part of a device code swap made by the user code verification endpoint. + PersistTokenPayload = context.EndpointType switch { - ClientId = context.ClientId, - CreateTokenEntry = !context.Options.DisableTokenStorage, - // Access tokens can be converted to reference tokens if the - // corresponding option was enabled in the server options. - PersistTokenPayload = context.Options.UseReferenceAccessTokens, - Principal = context.AccessTokenPrincipal!, - TokenType = TokenTypeHints.AccessToken - }; + OpenIddictServerEndpointType.Verification => false, - await _dispatcher.DispatchAsync(notification); + _ => !context.Options.DisableTokenStorage + }, + Principal = context.DeviceCodePrincipal!, + TokenType = TokenTypeHints.DeviceCode + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.AccessToken = notification.Token; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.DeviceCode = notification.Token; } + } + + /// + /// Contains the logic responsible of generating a refresh token for the current sign-in operation. + /// + public class GenerateRefreshToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public GenerateRefreshToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of generating an authorization code for the current sign-in operation. + /// Gets the default descriptor definition assigned to this handler. /// - public class GenerateAuthorizationCode : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(GenerateDeviceCode.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessSignInContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public GenerateAuthorizationCode(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(GenerateAccessToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new GenerateTokenContext(context.Transaction) - { - ClientId = context.ClientId, - CreateTokenEntry = !context.Options.DisableTokenStorage, - PersistTokenPayload = !context.Options.DisableTokenStorage, - Principal = context.AuthorizationCodePrincipal!, - TokenType = TokenTypeHints.AuthorizationCode - }; - - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + var notification = new GenerateTokenContext(context.Transaction) + { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + // Refresh tokens can be converted to reference tokens if the + // corresponding option was enabled in the server options. + PersistTokenPayload = context.Options.UseReferenceRefreshTokens, + Principal = context.RefreshTokenPrincipal!, + TokenType = TokenTypeHints.RefreshToken + }; + + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.AuthorizationCode = notification.Token; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.RefreshToken = notification.Token; } + } + /// + /// Contains the logic responsible of generating and attaching the device code identifier to the user code principal. + /// + public class AttachDeviceCodeIdentifier : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of generating a device code for the current sign-in operation. + /// Gets the default descriptor definition assigned to this handler. /// - public class GenerateDeviceCode : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(GenerateRefreshToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public GenerateDeviceCode(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(GenerateAuthorizationCode.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new GenerateTokenContext(context.Transaction) - { - ClientId = context.ClientId, - CreateTokenEntry = !context.Options.DisableTokenStorage, - // Device codes can be converted to reference tokens if they are not generated - // as part of a device code swap made by the user code verification endpoint. - PersistTokenPayload = context.EndpointType switch - { - OpenIddictServerEndpointType.Verification => false, + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - _ => !context.Options.DisableTokenStorage - }, - Principal = context.DeviceCodePrincipal!, - TokenType = TokenTypeHints.DeviceCode - }; + if (context.UserCodePrincipal is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0020)); + } - await _dispatcher.DispatchAsync(notification); + var identifier = context.DeviceCodePrincipal?.GetTokenId(); + if (!string.IsNullOrEmpty(identifier)) + { + context.UserCodePrincipal.SetClaim(Claims.Private.DeviceCodeId, identifier); + } - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + return default; + } + } - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + /// + /// Contains the logic responsible of updating the existing reference device code entry. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class UpdateReferenceDeviceCodeEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictTokenManager _tokenManager; - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + public UpdateReferenceDeviceCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - context.DeviceCode = notification.Token; - } - } + public UpdateReferenceDeviceCodeEntry(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// - /// Contains the logic responsible of generating a refresh token for the current sign-in operation. + /// Gets the default descriptor definition assigned to this handler. /// - public class GenerateRefreshToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(AttachDeviceCodeIdentifier.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessSignInContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public GenerateRefreshToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(GenerateDeviceCode.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var notification = new GenerateTokenContext(context.Transaction) - { - ClientId = context.ClientId, - CreateTokenEntry = !context.Options.DisableTokenStorage, - // Refresh tokens can be converted to reference tokens if the - // corresponding option was enabled in the server options. - PersistTokenPayload = context.Options.UseReferenceRefreshTokens, - Principal = context.RefreshTokenPrincipal!, - TokenType = TokenTypeHints.RefreshToken - }; + if (context.EndpointType != OpenIddictServerEndpointType.Verification || string.IsNullOrEmpty(context.DeviceCode)) + { + return; + } - await _dispatcher.DispatchAsync(notification); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } - - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (context.DeviceCodePrincipal is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0020)); + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + // Extract the token identifier from the authentication principal. + var identifier = context.Principal.GetClaim(Claims.Private.DeviceCodeId); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0008)); + } - context.RefreshToken = notification.Token; + var token = await _tokenManager.FindByIdAsync(identifier); + if (token is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0265)); } - } - /// - /// Contains the logic responsible of generating and attaching the device code identifier to the user code principal. - /// - public class AttachDeviceCodeIdentifier : IOpenIddictServerHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(GenerateRefreshToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + // Replace the device code details by the payload derived from the new device code principal, + // that includes all the user claims populated by the application after authenticating the user. + var descriptor = new OpenIddictTokenDescriptor(); + await _tokenManager.PopulateAsync(descriptor, token); - if (context.UserCodePrincipal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0020)); - } + // Note: the lifetime is deliberately extended to give more time to the client to redeem the code. + descriptor.ExpirationDate = context.DeviceCodePrincipal.GetExpirationDate(); + descriptor.Payload = context.DeviceCode; + descriptor.Principal = context.DeviceCodePrincipal; + descriptor.Status = Statuses.Valid; + descriptor.Subject = context.DeviceCodePrincipal.GetClaim(Claims.Subject); - var identifier = context.DeviceCodePrincipal?.GetTokenId(); - if (!string.IsNullOrEmpty(identifier)) - { - context.UserCodePrincipal.SetClaim(Claims.Private.DeviceCodeId, identifier); - } + await _tokenManager.UpdateAsync(token, descriptor); - return default; - } + context.Logger.LogTrace(SR.GetResourceString(SR.ID6021), await _tokenManager.GetIdAsync(token)); } + } + /// + /// Contains the logic responsible of generating and attaching the hashes of + /// the access token and authorization code to the identity token principal. + /// + public class AttachTokenDigests : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of updating the existing reference device code entry. - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class UpdateReferenceDeviceCodeEntry : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(UpdateReferenceDeviceCodeEntry.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - private readonly IOpenIddictTokenManager _tokenManager; - - public UpdateReferenceDeviceCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); - - public UpdateReferenceDeviceCodeEntry(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(AttachDeviceCodeIdentifier.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.EndpointType != OpenIddictServerEndpointType.Verification || string.IsNullOrEmpty(context.DeviceCode)) - { - return; - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - - if (context.DeviceCodePrincipal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0020)); - } - - // Extract the token identifier from the authentication principal. - var identifier = context.Principal.GetClaim(Claims.Private.DeviceCodeId); - if (string.IsNullOrEmpty(identifier)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0008)); - } - - var token = await _tokenManager.FindByIdAsync(identifier); - if (token is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0265)); - } - - // Replace the device code details by the payload derived from the new device code principal, - // that includes all the user claims populated by the application after authenticating the user. - var descriptor = new OpenIddictTokenDescriptor(); - await _tokenManager.PopulateAsync(descriptor, token); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // Note: the lifetime is deliberately extended to give more time to the client to redeem the code. - descriptor.ExpirationDate = context.DeviceCodePrincipal.GetExpirationDate(); - descriptor.Payload = context.DeviceCode; - descriptor.Principal = context.DeviceCodePrincipal; - descriptor.Status = Statuses.Valid; - descriptor.Subject = context.DeviceCodePrincipal.GetClaim(Claims.Subject); + if (context.IdentityTokenPrincipal is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0022)); + } - await _tokenManager.UpdateAsync(token, descriptor); + if (string.IsNullOrEmpty(context.AccessToken) && string.IsNullOrEmpty(context.AuthorizationCode)) + { + return default; + } - context.Logger.LogTrace(SR.GetResourceString(SR.ID6021), await _tokenManager.GetIdAsync(token)); + var credentials = context.Options.SigningCredentials.FirstOrDefault( + credentials => credentials.Key is AsymmetricSecurityKey); + if (credentials is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0266)); } - } - /// - /// Contains the logic responsible of generating and attaching the hashes of - /// the access token and authorization code to the identity token principal. - /// - public class AttachTokenDigests : IOpenIddictServerHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(UpdateReferenceDeviceCodeEntry.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + using var hash = GetHashAlgorithm(credentials); + if (hash is null || hash is KeyedHashAlgorithm) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)); + } - if (context.IdentityTokenPrincipal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0022)); - } + if (!string.IsNullOrEmpty(context.AccessToken)) + { + var digest = hash.ComputeHash(Encoding.ASCII.GetBytes(context.AccessToken)); - if (string.IsNullOrEmpty(context.AccessToken) && string.IsNullOrEmpty(context.AuthorizationCode)) - { - return default; - } + // Note: only the left-most half of the hash is used. + // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken + context.IdentityTokenPrincipal.SetClaim(Claims.AccessTokenHash, Base64UrlEncoder.Encode(digest, 0, digest.Length / 2)); + } - var credentials = context.Options.SigningCredentials.FirstOrDefault( - credentials => credentials.Key is AsymmetricSecurityKey); - if (credentials is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0266)); - } + if (!string.IsNullOrEmpty(context.AuthorizationCode)) + { + var digest = hash.ComputeHash(Encoding.ASCII.GetBytes(context.AuthorizationCode)); - using var hash = GetHashAlgorithm(credentials); - if (hash is null || hash is KeyedHashAlgorithm) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)); - } + // Note: only the left-most half of the hash is used. + // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken + context.IdentityTokenPrincipal.SetClaim(Claims.CodeHash, Base64UrlEncoder.Encode(digest, 0, digest.Length / 2)); + } - if (!string.IsNullOrEmpty(context.AccessToken)) - { - var digest = hash.ComputeHash(Encoding.ASCII.GetBytes(context.AccessToken)); + return default; - // Note: only the left-most half of the hash is used. - // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken - context.IdentityTokenPrincipal.SetClaim(Claims.AccessTokenHash, Base64UrlEncoder.Encode(digest, 0, digest.Length / 2)); - } + static HashAlgorithm? GetHashAlgorithm(SigningCredentials credentials) + { + HashAlgorithm? hash = null; - if (!string.IsNullOrEmpty(context.AuthorizationCode)) + if (!string.IsNullOrEmpty(credentials.Digest)) { - var digest = hash.ComputeHash(Encoding.ASCII.GetBytes(context.AuthorizationCode)); - - // Note: only the left-most half of the hash is used. - // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken - context.IdentityTokenPrincipal.SetClaim(Claims.CodeHash, Base64UrlEncoder.Encode(digest, 0, digest.Length / 2)); + hash = CryptoConfig.CreateFromName(credentials.Digest) as HashAlgorithm; } - return default; - - static HashAlgorithm? GetHashAlgorithm(SigningCredentials credentials) + if (hash is null) { - HashAlgorithm? hash = null; - - if (!string.IsNullOrEmpty(credentials.Digest)) + var algorithm = credentials.Digest switch { - hash = CryptoConfig.CreateFromName(credentials.Digest) as HashAlgorithm; - } + SecurityAlgorithms.Sha256 or SecurityAlgorithms.Sha256Digest => HashAlgorithmName.SHA256, + SecurityAlgorithms.Sha384 or SecurityAlgorithms.Sha384Digest => HashAlgorithmName.SHA384, + SecurityAlgorithms.Sha512 or SecurityAlgorithms.Sha512Digest => HashAlgorithmName.SHA512, - if (hash is null) - { - var algorithm = credentials.Digest switch + _ => credentials.Algorithm switch { - SecurityAlgorithms.Sha256 or SecurityAlgorithms.Sha256Digest => HashAlgorithmName.SHA256, - SecurityAlgorithms.Sha384 or SecurityAlgorithms.Sha384Digest => HashAlgorithmName.SHA384, - SecurityAlgorithms.Sha512 or SecurityAlgorithms.Sha512Digest => HashAlgorithmName.SHA512, - - _ => credentials.Algorithm switch - { #if SUPPORTS_ECDSA - SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature - => HashAlgorithmName.SHA256, - SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature - => HashAlgorithmName.SHA384, - SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature - => HashAlgorithmName.SHA512, + SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature + => HashAlgorithmName.SHA256, + SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature + => HashAlgorithmName.SHA384, + SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature + => HashAlgorithmName.SHA512, #endif - SecurityAlgorithms.HmacSha256 or SecurityAlgorithms.HmacSha256Signature - => HashAlgorithmName.SHA256, - SecurityAlgorithms.HmacSha384 or SecurityAlgorithms.HmacSha384Signature - => HashAlgorithmName.SHA384, - SecurityAlgorithms.HmacSha512 or SecurityAlgorithms.HmacSha512Signature - => HashAlgorithmName.SHA512, - - SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature - => HashAlgorithmName.SHA256, - SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature - => HashAlgorithmName.SHA384, - SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature - => HashAlgorithmName.SHA512, - - SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature - => HashAlgorithmName.SHA256, - SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature - => HashAlgorithmName.SHA384, - SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature - => HashAlgorithmName.SHA512, - - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)) - } - }; - - hash = CryptoConfig.CreateFromName(algorithm.Name!) as HashAlgorithm; - } + SecurityAlgorithms.HmacSha256 or SecurityAlgorithms.HmacSha256Signature + => HashAlgorithmName.SHA256, + SecurityAlgorithms.HmacSha384 or SecurityAlgorithms.HmacSha384Signature + => HashAlgorithmName.SHA384, + SecurityAlgorithms.HmacSha512 or SecurityAlgorithms.HmacSha512Signature + => HashAlgorithmName.SHA512, + + SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature + => HashAlgorithmName.SHA256, + SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature + => HashAlgorithmName.SHA384, + SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature + => HashAlgorithmName.SHA512, + + SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature + => HashAlgorithmName.SHA256, + SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature + => HashAlgorithmName.SHA384, + SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature + => HashAlgorithmName.SHA512, + + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)) + } + }; - return hash; + hash = CryptoConfig.CreateFromName(algorithm.Name!) as HashAlgorithm; } + + return hash; } } + } + + /// + /// Contains the logic responsible of generating a user code for the current sign-in operation. + /// + public class GenerateUserCode : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public GenerateUserCode(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of generating a user code for the current sign-in operation. + /// Gets the default descriptor definition assigned to this handler. /// - public class GenerateUserCode : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(AttachTokenDigests.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessSignInContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public GenerateUserCode(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(AttachTokenDigests.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new GenerateTokenContext(context.Transaction) - { - ClientId = context.ClientId, - CreateTokenEntry = !context.Options.DisableTokenStorage, - PersistTokenPayload = !context.Options.DisableTokenStorage, - Principal = context.UserCodePrincipal!, - TokenType = TokenTypeHints.UserCode - }; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - await _dispatcher.DispatchAsync(notification); + var notification = new GenerateTokenContext(context.Transaction) + { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + PersistTokenPayload = !context.Options.DisableTokenStorage, + Principal = context.UserCodePrincipal!, + TokenType = TokenTypeHints.UserCode + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.UserCode = notification.Token; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.UserCode = notification.Token; } + } + + /// + /// Contains the logic responsible of generating an identity token for the current sign-in operation. + /// + public class GenerateIdentityToken : IOpenIddictServerHandler + { + private readonly IOpenIddictServerDispatcher _dispatcher; + + public GenerateIdentityToken(IOpenIddictServerDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of generating an identity token for the current sign-in operation. + /// Gets the default descriptor definition assigned to this handler. /// - public class GenerateIdentityToken : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(GenerateUserCode.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessSignInContext context) { - private readonly IOpenIddictServerDispatcher _dispatcher; - - public GenerateIdentityToken(IOpenIddictServerDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(GenerateUserCode.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var notification = new GenerateTokenContext(context.Transaction) - { - ClientId = context.ClientId, - CreateTokenEntry = !context.Options.DisableTokenStorage, - // Identity tokens cannot never be reference tokens. - PersistTokenPayload = false, - Principal = context.IdentityTokenPrincipal!, - TokenType = TokenTypeHints.IdToken - }; + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - await _dispatcher.DispatchAsync(notification); + var notification = new GenerateTokenContext(context.Transaction) + { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + // Identity tokens cannot never be reference tokens. + PersistTokenPayload = false, + Principal = context.IdentityTokenPrincipal!, + TokenType = TokenTypeHints.IdToken + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.IdentityToken = notification.Token; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.IdentityToken = notification.Token; } + } + /// + /// Contains the logic responsible of attaching the appropriate parameters to the sign-in response. + /// + public class AttachSignInParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the appropriate parameters to the sign-in response. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachSignInParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(GenerateIdentityToken.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignInContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(GenerateIdentityToken.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignInContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.IncludeAccessToken) - { - context.Response.AccessToken = context.AccessToken; - context.Response.TokenType = TokenTypes.Bearer; + if (context.IncludeAccessToken) + { + context.Response.AccessToken = context.AccessToken; + context.Response.TokenType = TokenTypes.Bearer; - // If the principal is available, attach additional metadata. - if (context.AccessTokenPrincipal is not null) + // If the principal is available, attach additional metadata. + if (context.AccessTokenPrincipal is not null) + { + // If an expiration date was set on the access token principal, return it to the client application. + var date = context.AccessTokenPrincipal.GetExpirationDate(); + if (date.HasValue && date.Value > DateTimeOffset.UtcNow) { - // If an expiration date was set on the access token principal, return it to the client application. - var date = context.AccessTokenPrincipal.GetExpirationDate(); - if (date.HasValue && date.Value > DateTimeOffset.UtcNow) - { - context.Response.ExpiresIn = (long) ((date.Value - DateTimeOffset.UtcNow).TotalSeconds + .5); - } + context.Response.ExpiresIn = (long) ((date.Value - DateTimeOffset.UtcNow).TotalSeconds + .5); + } - // If the granted access token scopes differ from the requested scopes, return the granted scopes - // list as a parameter to inform the client application of the fact the scopes set will be reduced. - var scopes = new HashSet(context.AccessTokenPrincipal.GetScopes(), StringComparer.Ordinal); - if ((context.EndpointType == OpenIddictServerEndpointType.Token && context.Request.IsAuthorizationCodeGrantType()) || - !scopes.SetEquals(context.Request.GetScopes())) - { - context.Response.Scope = string.Join(" ", scopes); - } + // If the granted access token scopes differ from the requested scopes, return the granted scopes + // list as a parameter to inform the client application of the fact the scopes set will be reduced. + var scopes = new HashSet(context.AccessTokenPrincipal.GetScopes(), StringComparer.Ordinal); + if ((context.EndpointType == OpenIddictServerEndpointType.Token && context.Request.IsAuthorizationCodeGrantType()) || + !scopes.SetEquals(context.Request.GetScopes())) + { + context.Response.Scope = string.Join(" ", scopes); } } + } - if (context.IncludeAuthorizationCode) - { - context.Response.Code = context.AuthorizationCode; - } + if (context.IncludeAuthorizationCode) + { + context.Response.Code = context.AuthorizationCode; + } - if (context.IncludeDeviceCode) - { - context.Response.DeviceCode = context.DeviceCode; + if (context.IncludeDeviceCode) + { + context.Response.DeviceCode = context.DeviceCode; - // If the principal is available, attach additional metadata. - if (context.DeviceCodePrincipal is not null) + // If the principal is available, attach additional metadata. + if (context.DeviceCodePrincipal is not null) + { + // If an expiration date was set on the device code principal, return it to the client application. + var date = context.DeviceCodePrincipal.GetExpirationDate(); + if (date.HasValue && date.Value > DateTimeOffset.UtcNow) { - // If an expiration date was set on the device code principal, return it to the client application. - var date = context.DeviceCodePrincipal.GetExpirationDate(); - if (date.HasValue && date.Value > DateTimeOffset.UtcNow) - { - context.Response.ExpiresIn = (long) ((date.Value - DateTimeOffset.UtcNow).TotalSeconds + .5); - } + context.Response.ExpiresIn = (long) ((date.Value - DateTimeOffset.UtcNow).TotalSeconds + .5); } } + } - if (context.IncludeIdentityToken) - { - context.Response.IdToken = context.IdentityToken; - } + if (context.IncludeIdentityToken) + { + context.Response.IdToken = context.IdentityToken; + } - if (context.IncludeRefreshToken) - { - context.Response.RefreshToken = context.RefreshToken; - } + if (context.IncludeRefreshToken) + { + context.Response.RefreshToken = context.RefreshToken; + } - if (context.IncludeUserCode) - { - context.Response.UserCode = context.UserCode; + if (context.IncludeUserCode) + { + context.Response.UserCode = context.UserCode; - var address = GetEndpointAbsoluteUri(context.Issuer, context.Options.VerificationEndpointUris.FirstOrDefault()); - if (address is not null) + var address = GetEndpointAbsoluteUri(context.Issuer, context.Options.VerificationEndpointUris.FirstOrDefault()); + if (address is not null) + { + var builder = new UriBuilder(address) { - var builder = new UriBuilder(address) - { - Query = string.Concat(Parameters.UserCode, "=", context.UserCode) - }; + Query = string.Concat(Parameters.UserCode, "=", context.UserCode) + }; - context.Response[Parameters.VerificationUri] = address.AbsoluteUri; - context.Response[Parameters.VerificationUriComplete] = builder.Uri.AbsoluteUri; - } + context.Response[Parameters.VerificationUri] = address.AbsoluteUri; + context.Response[Parameters.VerificationUriComplete] = builder.Uri.AbsoluteUri; } + } - if (context.Parameters.Count > 0) + if (context.Parameters.Count > 0) + { + foreach (var parameter in context.Parameters) { - foreach (var parameter in context.Parameters) - { - context.Response.SetParameter(parameter.Key, parameter.Value); - } + context.Response.SetParameter(parameter.Key, parameter.Value); } + } - return default; + return default; - static Uri? GetEndpointAbsoluteUri(Uri? issuer, Uri? endpoint) + static Uri? GetEndpointAbsoluteUri(Uri? issuer, Uri? endpoint) + { + // If the endpoint is disabled (i.e a null address is specified), return null. + if (endpoint is null) { - // If the endpoint is disabled (i.e a null address is specified), return null. - if (endpoint is null) - { - return null; - } - - // If the endpoint address is already an absolute URL, return it as-is. - if (endpoint.IsAbsoluteUri) - { - return endpoint; - } + return null; + } - // At this stage, throw an exception if the issuer cannot be retrieved. - if (issuer is null || !issuer.IsAbsoluteUri) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0023)); - } + // If the endpoint address is already an absolute URL, return it as-is. + if (endpoint.IsAbsoluteUri) + { + return endpoint; + } - // Ensure the issuer ends with a trailing slash, as it is necessary - // for Uri's constructor to correctly compute correct absolute URLs. - if (!issuer.OriginalString.EndsWith("/", StringComparison.Ordinal)) - { - issuer = new Uri(issuer.OriginalString + "/", UriKind.Absolute); - } + // At this stage, throw an exception if the issuer cannot be retrieved. + if (issuer is null || !issuer.IsAbsoluteUri) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0023)); + } - // Ensure the endpoint does not start with a leading slash, as it is necessary - // for Uri's constructor to correctly compute correct absolute URLs. - if (endpoint.OriginalString.StartsWith("/", StringComparison.Ordinal)) - { - endpoint = new Uri(endpoint.OriginalString.Substring(1, endpoint.OriginalString.Length - 1), UriKind.Relative); - } + // Ensure the issuer ends with a trailing slash, as it is necessary + // for Uri's constructor to correctly compute correct absolute URLs. + if (!issuer.OriginalString.EndsWith("/", StringComparison.Ordinal)) + { + issuer = new Uri(issuer.OriginalString + "/", UriKind.Absolute); + } - return new Uri(issuer, endpoint); + // Ensure the endpoint does not start with a leading slash, as it is necessary + // for Uri's constructor to correctly compute correct absolute URLs. + if (endpoint.OriginalString.StartsWith("/", StringComparison.Ordinal)) + { + endpoint = new Uri(endpoint.OriginalString.Substring(1, endpoint.OriginalString.Length - 1), UriKind.Relative); } + + return new Uri(issuer, endpoint); } } + } + /// + /// Contains the logic responsible of ensuring that the sign-out demand + /// is compatible with the type of the endpoint that handled the request. + /// + public class ValidateSignOutDemand : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of ensuring that the sign-out demand - /// is compatible with the type of the endpoint that handled the request. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateSignOutDemand : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignOutContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignOutContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.EndpointType != OpenIddictServerEndpointType.Logout) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0024)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - return default; + if (context.EndpointType != OpenIddictServerEndpointType.Logout) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0024)); } + + return default; } + } + /// + /// Contains the logic responsible of attaching the appropriate parameters to the sign-out response. + /// + public class AttachSignOutParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the appropriate parameters to the sign-out response. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachSignOutParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateSignOutDemand.Descriptor.Order + 1_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessSignOutContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateSignOutDemand.Descriptor.Order + 1_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessSignOutContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.Parameters.Count > 0) + if (context.Parameters.Count > 0) + { + foreach (var parameter in context.Parameters) { - foreach (var parameter in context.Parameters) - { - context.Response.SetParameter(parameter.Key, parameter.Value); - } + context.Response.SetParameter(parameter.Key, parameter.Value); } - - return default; } + + return default; } + } + /// + /// Contains the logic responsible of attaching the appropriate parameters to the error response. + /// + public class AttachErrorParameters : IOpenIddictServerHandler + { /// - /// Contains the logic responsible of attaching the appropriate parameters to the error response. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachErrorParameters : IOpenIddictServerHandler + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessErrorContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictServerHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessErrorContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Response.Error = context.Error; - context.Response.ErrorDescription = context.ErrorDescription; - context.Response.ErrorUri = context.ErrorUri; + context.Response.Error = context.Error; + context.Response.ErrorDescription = context.ErrorDescription; + context.Response.ErrorUri = context.ErrorUri; - if (context.Parameters.Count > 0) + if (context.Parameters.Count > 0) + { + foreach (var parameter in context.Parameters) { - foreach (var parameter in context.Parameters) - { - context.Response.SetParameter(parameter.Key, parameter.Value); - } + context.Response.SetParameter(parameter.Key, parameter.Value); } - - return default; } + + return default; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHelpers.cs b/src/OpenIddict.Server/OpenIddictServerHelpers.cs index 7e07acb3..b3d60b8e 100644 --- a/src/OpenIddict.Server/OpenIddictServerHelpers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHelpers.cs @@ -7,74 +7,73 @@ using System; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +/// +/// Exposes extensions simplifying the integration with the OpenIddict server services. +/// +public static class OpenIddictServerHelpers { /// - /// Exposes extensions simplifying the integration with the OpenIddict server services. + /// Retrieves a property value from the server transaction using the specified name. /// - public static class OpenIddictServerHelpers + /// The type of the property. + /// The server transaction. + /// The property name. + /// The property value or null if it couldn't be found. + public static TProperty? GetProperty( + this OpenIddictServerTransaction transaction, string name) where TProperty : class { - /// - /// Retrieves a property value from the server transaction using the specified name. - /// - /// The type of the property. - /// The server transaction. - /// The property name. - /// The property value or null if it couldn't be found. - public static TProperty? GetProperty( - this OpenIddictServerTransaction transaction, string name) where TProperty : class + if (transaction is null) { - if (transaction is null) - { - throw new ArgumentNullException(nameof(transaction)); - } - - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0106), nameof(name)); - } - - if (transaction.Properties.TryGetValue(name, out var property) && property is TProperty result) - { - return result; - } + throw new ArgumentNullException(nameof(transaction)); + } - return null; + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0106), nameof(name)); } - /// - /// Sets a property in the server transaction using the specified name and value. - /// - /// The type of the property. - /// The server transaction. - /// The property name. - /// The property value. - /// The server transaction, so that calls can be easily chained. - public static OpenIddictServerTransaction SetProperty( - this OpenIddictServerTransaction transaction, - string name, TProperty? value) where TProperty : class + if (transaction.Properties.TryGetValue(name, out var property) && property is TProperty result) { - if (transaction is null) - { - throw new ArgumentNullException(nameof(transaction)); - } + return result; + } - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0106), nameof(name)); - } + return null; + } - if (value is null) - { - transaction.Properties.Remove(name); - } + /// + /// Sets a property in the server transaction using the specified name and value. + /// + /// The type of the property. + /// The server transaction. + /// The property name. + /// The property value. + /// The server transaction, so that calls can be easily chained. + public static OpenIddictServerTransaction SetProperty( + this OpenIddictServerTransaction transaction, + string name, TProperty? value) where TProperty : class + { + if (transaction is null) + { + throw new ArgumentNullException(nameof(transaction)); + } - else - { - transaction.Properties[name] = value; - } + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0106), nameof(name)); + } - return transaction; + if (value is null) + { + transaction.Properties.Remove(name); } + + else + { + transaction.Properties[name] = value; + } + + return transaction; } } diff --git a/src/OpenIddict.Server/OpenIddictServerOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs index 5a9e7196..f42413e8 100644 --- a/src/OpenIddict.Server/OpenIddictServerOptions.cs +++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs @@ -14,386 +14,385 @@ using OpenIddict.Abstractions; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +/// +/// Provides various settings needed to configure the OpenIddict server handler. +/// +public class OpenIddictServerOptions { /// - /// Provides various settings needed to configure the OpenIddict server handler. + /// Gets or sets the optional base address used to uniquely identify the authorization server. + /// The URI must be absolute and may contain a path, but no query string or fragment part. + /// + public Uri? Issuer { get; set; } + + /// + /// Gets the list of encryption credentials used by the OpenIddict server services. + /// Multiple credentials can be added to support key rollover, but if X.509 keys + /// are used, at least one of them must have a valid creation/expiration date. + /// Note: the encryption credentials are not used to protect/unprotect tokens issued + /// by ASP.NET Core Data Protection, that uses its own key ring, configured separately. + /// + /// + /// Note: OpenIddict automatically sorts the credentials based on the following algorithm: + /// + /// Symmetric keys are always preferred when they can be used for the operation (e.g token encryption). + /// X.509 keys are always preferred to non-X.509 asymmetric keys. + /// X.509 keys with the furthest expiration date are preferred. + /// X.509 keys whose backing certificate is not yet valid are never preferred. + /// + /// + public List EncryptionCredentials { get; } = new(); + + /// + /// Gets the list of signing credentials used by the OpenIddict server services. + /// Multiple credentials can be added to support key rollover, but if X.509 keys + /// are used, at least one of them must have a valid creation/expiration date. + /// Note: the signing credentials are not used to protect/unprotect tokens issued + /// by ASP.NET Core Data Protection, that uses its own key ring, configured separately. + /// + /// + /// Note: OpenIddict automatically sorts the credentials based on the following algorithm: + /// + /// Symmetric keys are always preferred when they can be used for the operation (e.g token signing). + /// X.509 keys are always preferred to non-X.509 asymmetric keys. + /// X.509 keys with the furthest expiration date are preferred. + /// X.509 keys whose backing certificate is not yet valid are never preferred. + /// + /// + public List SigningCredentials { get; } = new(); + + /// + /// Gets the absolute and relative URIs associated to the authorization endpoint. /// - public class OpenIddictServerOptions + public List AuthorizationEndpointUris { get; } = new(); + + /// + /// Gets the absolute and relative URIs associated to the configuration endpoint. + /// + public List ConfigurationEndpointUris { get; } = new() { - /// - /// Gets or sets the optional base address used to uniquely identify the authorization server. - /// The URI must be absolute and may contain a path, but no query string or fragment part. - /// - public Uri? Issuer { get; set; } - - /// - /// Gets the list of encryption credentials used by the OpenIddict server services. - /// Multiple credentials can be added to support key rollover, but if X.509 keys - /// are used, at least one of them must have a valid creation/expiration date. - /// Note: the encryption credentials are not used to protect/unprotect tokens issued - /// by ASP.NET Core Data Protection, that uses its own key ring, configured separately. - /// - /// - /// Note: OpenIddict automatically sorts the credentials based on the following algorithm: - /// - /// Symmetric keys are always preferred when they can be used for the operation (e.g token encryption). - /// X.509 keys are always preferred to non-X.509 asymmetric keys. - /// X.509 keys with the furthest expiration date are preferred. - /// X.509 keys whose backing certificate is not yet valid are never preferred. - /// - /// - public List EncryptionCredentials { get; } = new(); - - /// - /// Gets the list of signing credentials used by the OpenIddict server services. - /// Multiple credentials can be added to support key rollover, but if X.509 keys - /// are used, at least one of them must have a valid creation/expiration date. - /// Note: the signing credentials are not used to protect/unprotect tokens issued - /// by ASP.NET Core Data Protection, that uses its own key ring, configured separately. - /// - /// - /// Note: OpenIddict automatically sorts the credentials based on the following algorithm: - /// - /// Symmetric keys are always preferred when they can be used for the operation (e.g token signing). - /// X.509 keys are always preferred to non-X.509 asymmetric keys. - /// X.509 keys with the furthest expiration date are preferred. - /// X.509 keys whose backing certificate is not yet valid are never preferred. - /// - /// - public List SigningCredentials { get; } = new(); - - /// - /// Gets the absolute and relative URIs associated to the authorization endpoint. - /// - public List AuthorizationEndpointUris { get; } = new(); - - /// - /// Gets the absolute and relative URIs associated to the configuration endpoint. - /// - public List ConfigurationEndpointUris { get; } = new() - { - new Uri("/.well-known/openid-configuration", UriKind.Relative), - new Uri("/.well-known/oauth-authorization-server", UriKind.Relative) - }; - - /// - /// Gets the absolute and relative URIs associated to the cryptography endpoint. - /// - public List CryptographyEndpointUris { get; } = new() - { - new Uri("/.well-known/jwks", UriKind.Relative) - }; - - /// - /// Gets the absolute and relative URIs associated to the device endpoint. - /// - public List DeviceEndpointUris { get; } = new(); - - /// - /// Gets the absolute and relative URIs associated to the introspection endpoint. - /// - public List IntrospectionEndpointUris { get; } = new(); - - /// - /// Gets the absolute and relative URIs associated to the logout endpoint. - /// - public List LogoutEndpointUris { get; } = new(); - - /// - /// Gets the absolute and relative URIs associated to the revocation endpoint. - /// - public List RevocationEndpointUris { get; } = new(); - - /// - /// Gets the absolute and relative URIs associated to the token endpoint. - /// - public List TokenEndpointUris { get; } = new(); - - /// - /// Gets the absolute and relative URIs associated to the userinfo endpoint. - /// - public List UserinfoEndpointUris { get; } = new(); - - /// - /// Gets the absolute and relative URIs associated to the verification endpoint. - /// - public List VerificationEndpointUris { get; } = new(); - - /// - /// Gets or sets the JWT handler used to protect and unprotect tokens. - /// - public JsonWebTokenHandler JsonWebTokenHandler { get; set; } = new() - { - SetDefaultTimesOnTokenCreation = false - }; + new Uri("/.well-known/openid-configuration", UriKind.Relative), + new Uri("/.well-known/oauth-authorization-server", UriKind.Relative) + }; - /// - /// Gets the token validation parameters used by the OpenIddict server services. - /// - public TokenValidationParameters TokenValidationParameters { get; } = new() + /// + /// Gets the absolute and relative URIs associated to the cryptography endpoint. + /// + public List CryptographyEndpointUris { get; } = new() + { + new Uri("/.well-known/jwks", UriKind.Relative) + }; + + /// + /// Gets the absolute and relative URIs associated to the device endpoint. + /// + public List DeviceEndpointUris { get; } = new(); + + /// + /// Gets the absolute and relative URIs associated to the introspection endpoint. + /// + public List IntrospectionEndpointUris { get; } = new(); + + /// + /// Gets the absolute and relative URIs associated to the logout endpoint. + /// + public List LogoutEndpointUris { get; } = new(); + + /// + /// Gets the absolute and relative URIs associated to the revocation endpoint. + /// + public List RevocationEndpointUris { get; } = new(); + + /// + /// Gets the absolute and relative URIs associated to the token endpoint. + /// + public List TokenEndpointUris { get; } = new(); + + /// + /// Gets the absolute and relative URIs associated to the userinfo endpoint. + /// + public List UserinfoEndpointUris { get; } = new(); + + /// + /// Gets the absolute and relative URIs associated to the verification endpoint. + /// + public List VerificationEndpointUris { get; } = new(); + + /// + /// Gets or sets the JWT handler used to protect and unprotect tokens. + /// + public JsonWebTokenHandler JsonWebTokenHandler { get; set; } = new() + { + SetDefaultTimesOnTokenCreation = false + }; + + /// + /// Gets the token validation parameters used by the OpenIddict server services. + /// + public TokenValidationParameters TokenValidationParameters { get; } = new() + { + AuthenticationType = TokenValidationParameters.DefaultAuthenticationType, + ClockSkew = TimeSpan.Zero, + NameClaimType = OpenIddictConstants.Claims.Name, + RoleClaimType = OpenIddictConstants.Claims.Role, + // In previous versions of OpenIddict (1.x and 2.x), all the JWT tokens (access and identity tokens) + // were issued with the generic "typ": "JWT" header. To prevent confused deputy and token substitution + // attacks, a special "token_usage" claim was added to the JWT payload to convey the actual token type. + // This validator overrides the default logic used by IdentityModel to resolve the type from this claim. + TypeValidator = (type, token, parameters) => { - AuthenticationType = TokenValidationParameters.DefaultAuthenticationType, - ClockSkew = TimeSpan.Zero, - NameClaimType = OpenIddictConstants.Claims.Name, - RoleClaimType = OpenIddictConstants.Claims.Role, - // In previous versions of OpenIddict (1.x and 2.x), all the JWT tokens (access and identity tokens) - // were issued with the generic "typ": "JWT" header. To prevent confused deputy and token substitution - // attacks, a special "token_usage" claim was added to the JWT payload to convey the actual token type. - // This validator overrides the default logic used by IdentityModel to resolve the type from this claim. - TypeValidator = (type, token, parameters) => + // If available, try to resolve the actual type from the "token_usage" claim. + if (((JsonWebToken) token).TryGetPayloadValue(OpenIddictConstants.Claims.TokenUsage, out string usage)) { - // If available, try to resolve the actual type from the "token_usage" claim. - if (((JsonWebToken) token).TryGetPayloadValue(OpenIddictConstants.Claims.TokenUsage, out string usage)) - { - type = usage switch - { - TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken, - TokenTypeHints.IdToken => JsonWebTokenTypes.IdentityToken, - - _ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269)) - }; - } - - // At this point, throw an exception if the type cannot be resolved from the "typ" header - // (provided via the type delegate parameter) or inferred from the token_usage claim. - if (string.IsNullOrEmpty(type)) + type = usage switch { - throw new SecurityTokenInvalidTypeException(SR.GetResourceString(SR.ID0270)); - } + TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken, + TokenTypeHints.IdToken => JsonWebTokenTypes.IdentityToken, - // Note: unlike IdentityModel, this custom validator deliberately uses case-insensitive comparisons. - if (parameters.ValidTypes is not null && parameters.ValidTypes.Any() && - !parameters.ValidTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) + _ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269)) + }; + } + + // At this point, throw an exception if the type cannot be resolved from the "typ" header + // (provided via the type delegate parameter) or inferred from the token_usage claim. + if (string.IsNullOrEmpty(type)) + { + throw new SecurityTokenInvalidTypeException(SR.GetResourceString(SR.ID0270)); + } + + // Note: unlike IdentityModel, this custom validator deliberately uses case-insensitive comparisons. + if (parameters.ValidTypes is not null && parameters.ValidTypes.Any() && + !parameters.ValidTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) + { + throw new SecurityTokenInvalidTypeException(SR.GetResourceString(SR.ID0271)) { - throw new SecurityTokenInvalidTypeException(SR.GetResourceString(SR.ID0271)) - { - InvalidType = type - }; - } - - return type; - }, - // Note: audience and lifetime are manually validated by OpenIddict itself. - ValidateAudience = false, - ValidateLifetime = false - }; - - /// - /// Gets or sets the period of time authorization codes remain valid after being issued. The default value is 5 minutes. - /// While not recommended, this property can be set to to issue authorization codes that never expire. - /// - public TimeSpan? AuthorizationCodeLifetime { get; set; } = TimeSpan.FromMinutes(5); - - /// - /// Gets or sets the period of time access tokens remain valid after being issued. The default value is 1 hour. - /// The client application is expected to refresh or acquire a new access token after the token has expired. - /// While not recommended, this property can be set to to issue access tokens that never expire. - /// - public TimeSpan? AccessTokenLifetime { get; set; } = TimeSpan.FromHours(1); - - /// - /// Gets or sets the period of time device codes remain valid after being issued. The default value is 10 minutes. - /// The client application is expected to start a whole new authentication flow after the device code has expired. - /// While not recommended, this property can be set to to issue device codes that never expire. - /// Note: the same value should be chosen for both and this property. - /// - /// - /// The expiration date of a device code is automatically extended when the user approves the - /// authorization demand to give the client application enough time to redeem the device code. - /// - public TimeSpan? DeviceCodeLifetime { get; set; } = TimeSpan.FromMinutes(10); - - /// - /// Gets or sets the period of time identity tokens remain valid after being issued. The default value is 20 minutes. - /// The client application is expected to refresh or acquire a new identity token after the token has expired. - /// While not recommended, this property can be set to to issue identity tokens that never expire. - /// - public TimeSpan? IdentityTokenLifetime { get; set; } = TimeSpan.FromMinutes(20); - - /// - /// Gets or sets the period of time refresh tokens remain valid after being issued. The default value is 14 days. - /// The client application is expected to start a whole new authentication flow after the refresh token has expired. - /// While not recommended, this property can be set to to issue refresh tokens that never expire. - /// - public TimeSpan? RefreshTokenLifetime { get; set; } = TimeSpan.FromDays(14); - - /// - /// Gets or sets the period of time rolling refresh tokens marked as redeemed can still be - /// used to make concurrent refresh token requests. The default value is 30 seconds. - /// - public TimeSpan? RefreshTokenReuseLeeway { get; set; } = TimeSpan.FromSeconds(30); - - /// - /// Gets or sets the period of time user codes remain valid after being issued. The default value is 10 minutes. - /// The client application is expected to start a whole new authentication flow after the user code has expired. - /// While not recommended, this property can be set to to issue user codes that never expire. - /// Note: the same value should be chosen for both and this property. - /// - public TimeSpan? UserCodeLifetime { get; set; } = TimeSpan.FromMinutes(10); - - /// - /// Gets or sets a boolean indicating whether the degraded mode is enabled. When this degraded mode - /// is enabled, all the security checks that depend on the OpenIddict core managers are disabled. - /// This option MUST be enabled with extreme caution and custom handlers MUST be registered to - /// properly validate OpenID Connect requests. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public bool EnableDegradedMode { get; set; } - - /// - /// Gets the list of the handlers responsible of processing the OpenIddict server operations. - /// Note: the list is automatically sorted based on the order assigned to each handler descriptor. - /// As such, it MUST NOT be mutated after options initialization to preserve the exact order. - /// - public List Handlers { get; } = new(OpenIddictServerHandlers.DefaultHandlers); - - /// - /// Gets or sets a boolean determining whether client identification is optional. - /// Enabling this option allows client applications to communicate with the token, - /// introspection and revocation endpoints without having to send their client identifier. - /// - public bool AcceptAnonymousClients { get; set; } - - /// - /// Gets the OAuth 2.0/OpenID Connect claims supported by this application. - /// - public HashSet Claims { get; } = new(StringComparer.Ordinal) - { - OpenIddictConstants.Claims.Audience, - OpenIddictConstants.Claims.ExpiresAt, - OpenIddictConstants.Claims.IssuedAt, - OpenIddictConstants.Claims.Issuer, - OpenIddictConstants.Claims.Subject - }; - - /// - /// Gets or sets a boolean indicating whether access token encryption should be disabled. - /// Disabling encryption is NOT recommended and SHOULD only be done when issuing tokens - /// to third-party resource servers/APIs you don't control and don't fully trust. - /// Note: disabling encryption has no effect when using ASP.NET Core Data Protection. - /// - public bool DisableAccessTokenEncryption { get; set; } - - /// - /// Gets or sets a boolean indicating whether authorization storage should be disabled. - /// When disabled, ad-hoc authorizations are not created when an authorization code or - /// refresh token is issued and can't be revoked to prevent associated tokens from being used. - /// - public bool DisableAuthorizationStorage { get; set; } - - /// - /// Gets or sets a boolean indicating whether rolling tokens are disabled. - /// When disabled, refresh tokens used in a token request are not marked - /// as redeemed and can still be used until they expire. Disabling - /// rolling refresh tokens is NOT recommended, for security reasons. - /// - public bool DisableRollingRefreshTokens { get; set; } - - /// - /// Gets or sets a boolean indicating whether sliding expiration is disabled - /// for refresh tokens. When this option is set to , - /// refresh tokens are issued with a fixed expiration date: when they expire, - /// a complete authorization flow must be started to retrieve a new refresh token. - /// - public bool DisableSlidingRefreshTokenExpiration { get; set; } - - /// - /// Gets or sets a boolean indicating whether token storage should be disabled. - /// When disabled, no database entry is created for the tokens and codes - /// returned by OpenIddict. Using this option is generally NOT recommended - /// as it prevents the tokens and codes from being revoked (if needed). - /// - public bool DisableTokenStorage { get; set; } - - /// - /// Gets or sets a boolean indicating whether scope validation is disabled. - /// - public bool DisableScopeValidation { get; set; } - - /// - /// Gets the OAuth 2.0 code challenge methods enabled for this application. - /// By default, only the S256 method is allowed (if the code flow is enabled). - /// - public HashSet CodeChallengeMethods { get; } = new(StringComparer.Ordinal); - - /// - /// Gets the OAuth 2.0/OpenID Connect flows enabled for this application. - /// - public HashSet GrantTypes { get; } = new(StringComparer.Ordinal); - - /// - /// Gets or sets a boolean indicating whether PKCE must be used by client applications - /// when requesting an authorization code (e.g when using the code or hybrid flows). - /// If this property is set to , authorization requests that - /// lack the code_challenge will be automatically rejected by OpenIddict. - /// - public bool RequireProofKeyForCodeExchange { get; set; } - - /// - /// Gets the OAuth 2.0/OpenID Connect response types enabled for this application. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public HashSet ResponseTypes { get; } = new(StringComparer.Ordinal); - - /// - /// Gets the OAuth 2.0/OpenID Connect response modes enabled for this application. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public HashSet ResponseModes { get; } = new(StringComparer.Ordinal); - - /// - /// Gets or sets a boolean indicating whether endpoint permissions should be ignored. - /// Setting this property to is NOT recommended. - /// - public bool IgnoreEndpointPermissions { get; set; } - - /// - /// Gets or sets a boolean indicating whether grant type permissions should be ignored. - /// Setting this property to is NOT recommended. - /// - public bool IgnoreGrantTypePermissions { get; set; } - - /// - /// Gets or sets a boolean indicating whether response type permissions should be ignored. - /// Setting this property to is NOT recommended. - /// - public bool IgnoreResponseTypePermissions { get; set; } - - /// - /// Gets or sets a boolean indicating whether scope permissions should be ignored. - /// Setting this property to is NOT recommended. - /// - public bool IgnoreScopePermissions { get; set; } - - /// - /// Gets the OAuth 2.0/OpenID Connect scopes enabled for this application. - /// - public HashSet Scopes { get; } = new(StringComparer.Ordinal) - { - OpenIddictConstants.Scopes.OpenId - }; - - /// - /// Gets or sets a boolean indicating whether reference access tokens should be used. - /// When set to , the token payload is stored in the database - /// and a crypto-secure random identifier is returned to the client application. - /// Enabling this option is useful when storing a very large number of claims - /// in the tokens, but it is RECOMMENDED to enable column encryption - /// in the database or use the ASP.NET Core Data Protection integration, - /// that provides additional protection against token leakage. - /// - public bool UseReferenceAccessTokens { get; set; } - - /// - /// Gets or sets a boolean indicating whether reference refresh tokens should be used. - /// When set to , the token payload is stored in the database - /// and a crypto-secure random identifier is returned to the client application. - /// Enabling this option is useful when storing a very large number of claims - /// in the tokens, but it is RECOMMENDED to enable column encryption - /// in the database or use the ASP.NET Core Data Protection integration, - /// that provides additional protection against token leakage. - /// - public bool UseReferenceRefreshTokens { get; set; } - } + InvalidType = type + }; + } + + return type; + }, + // Note: audience and lifetime are manually validated by OpenIddict itself. + ValidateAudience = false, + ValidateLifetime = false + }; + + /// + /// Gets or sets the period of time authorization codes remain valid after being issued. The default value is 5 minutes. + /// While not recommended, this property can be set to to issue authorization codes that never expire. + /// + public TimeSpan? AuthorizationCodeLifetime { get; set; } = TimeSpan.FromMinutes(5); + + /// + /// Gets or sets the period of time access tokens remain valid after being issued. The default value is 1 hour. + /// The client application is expected to refresh or acquire a new access token after the token has expired. + /// While not recommended, this property can be set to to issue access tokens that never expire. + /// + public TimeSpan? AccessTokenLifetime { get; set; } = TimeSpan.FromHours(1); + + /// + /// Gets or sets the period of time device codes remain valid after being issued. The default value is 10 minutes. + /// The client application is expected to start a whole new authentication flow after the device code has expired. + /// While not recommended, this property can be set to to issue device codes that never expire. + /// Note: the same value should be chosen for both and this property. + /// + /// + /// The expiration date of a device code is automatically extended when the user approves the + /// authorization demand to give the client application enough time to redeem the device code. + /// + public TimeSpan? DeviceCodeLifetime { get; set; } = TimeSpan.FromMinutes(10); + + /// + /// Gets or sets the period of time identity tokens remain valid after being issued. The default value is 20 minutes. + /// The client application is expected to refresh or acquire a new identity token after the token has expired. + /// While not recommended, this property can be set to to issue identity tokens that never expire. + /// + public TimeSpan? IdentityTokenLifetime { get; set; } = TimeSpan.FromMinutes(20); + + /// + /// Gets or sets the period of time refresh tokens remain valid after being issued. The default value is 14 days. + /// The client application is expected to start a whole new authentication flow after the refresh token has expired. + /// While not recommended, this property can be set to to issue refresh tokens that never expire. + /// + public TimeSpan? RefreshTokenLifetime { get; set; } = TimeSpan.FromDays(14); + + /// + /// Gets or sets the period of time rolling refresh tokens marked as redeemed can still be + /// used to make concurrent refresh token requests. The default value is 30 seconds. + /// + public TimeSpan? RefreshTokenReuseLeeway { get; set; } = TimeSpan.FromSeconds(30); + + /// + /// Gets or sets the period of time user codes remain valid after being issued. The default value is 10 minutes. + /// The client application is expected to start a whole new authentication flow after the user code has expired. + /// While not recommended, this property can be set to to issue user codes that never expire. + /// Note: the same value should be chosen for both and this property. + /// + public TimeSpan? UserCodeLifetime { get; set; } = TimeSpan.FromMinutes(10); + + /// + /// Gets or sets a boolean indicating whether the degraded mode is enabled. When this degraded mode + /// is enabled, all the security checks that depend on the OpenIddict core managers are disabled. + /// This option MUST be enabled with extreme caution and custom handlers MUST be registered to + /// properly validate OpenID Connect requests. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public bool EnableDegradedMode { get; set; } + + /// + /// Gets the list of the handlers responsible of processing the OpenIddict server operations. + /// Note: the list is automatically sorted based on the order assigned to each handler descriptor. + /// As such, it MUST NOT be mutated after options initialization to preserve the exact order. + /// + public List Handlers { get; } = new(OpenIddictServerHandlers.DefaultHandlers); + + /// + /// Gets or sets a boolean determining whether client identification is optional. + /// Enabling this option allows client applications to communicate with the token, + /// introspection and revocation endpoints without having to send their client identifier. + /// + public bool AcceptAnonymousClients { get; set; } + + /// + /// Gets the OAuth 2.0/OpenID Connect claims supported by this application. + /// + public HashSet Claims { get; } = new(StringComparer.Ordinal) + { + OpenIddictConstants.Claims.Audience, + OpenIddictConstants.Claims.ExpiresAt, + OpenIddictConstants.Claims.IssuedAt, + OpenIddictConstants.Claims.Issuer, + OpenIddictConstants.Claims.Subject + }; + + /// + /// Gets or sets a boolean indicating whether access token encryption should be disabled. + /// Disabling encryption is NOT recommended and SHOULD only be done when issuing tokens + /// to third-party resource servers/APIs you don't control and don't fully trust. + /// Note: disabling encryption has no effect when using ASP.NET Core Data Protection. + /// + public bool DisableAccessTokenEncryption { get; set; } + + /// + /// Gets or sets a boolean indicating whether authorization storage should be disabled. + /// When disabled, ad-hoc authorizations are not created when an authorization code or + /// refresh token is issued and can't be revoked to prevent associated tokens from being used. + /// + public bool DisableAuthorizationStorage { get; set; } + + /// + /// Gets or sets a boolean indicating whether rolling tokens are disabled. + /// When disabled, refresh tokens used in a token request are not marked + /// as redeemed and can still be used until they expire. Disabling + /// rolling refresh tokens is NOT recommended, for security reasons. + /// + public bool DisableRollingRefreshTokens { get; set; } + + /// + /// Gets or sets a boolean indicating whether sliding expiration is disabled + /// for refresh tokens. When this option is set to , + /// refresh tokens are issued with a fixed expiration date: when they expire, + /// a complete authorization flow must be started to retrieve a new refresh token. + /// + public bool DisableSlidingRefreshTokenExpiration { get; set; } + + /// + /// Gets or sets a boolean indicating whether token storage should be disabled. + /// When disabled, no database entry is created for the tokens and codes + /// returned by OpenIddict. Using this option is generally NOT recommended + /// as it prevents the tokens and codes from being revoked (if needed). + /// + public bool DisableTokenStorage { get; set; } + + /// + /// Gets or sets a boolean indicating whether scope validation is disabled. + /// + public bool DisableScopeValidation { get; set; } + + /// + /// Gets the OAuth 2.0 code challenge methods enabled for this application. + /// By default, only the S256 method is allowed (if the code flow is enabled). + /// + public HashSet CodeChallengeMethods { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the OAuth 2.0/OpenID Connect flows enabled for this application. + /// + public HashSet GrantTypes { get; } = new(StringComparer.Ordinal); + + /// + /// Gets or sets a boolean indicating whether PKCE must be used by client applications + /// when requesting an authorization code (e.g when using the code or hybrid flows). + /// If this property is set to , authorization requests that + /// lack the code_challenge will be automatically rejected by OpenIddict. + /// + public bool RequireProofKeyForCodeExchange { get; set; } + + /// + /// Gets the OAuth 2.0/OpenID Connect response types enabled for this application. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public HashSet ResponseTypes { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the OAuth 2.0/OpenID Connect response modes enabled for this application. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public HashSet ResponseModes { get; } = new(StringComparer.Ordinal); + + /// + /// Gets or sets a boolean indicating whether endpoint permissions should be ignored. + /// Setting this property to is NOT recommended. + /// + public bool IgnoreEndpointPermissions { get; set; } + + /// + /// Gets or sets a boolean indicating whether grant type permissions should be ignored. + /// Setting this property to is NOT recommended. + /// + public bool IgnoreGrantTypePermissions { get; set; } + + /// + /// Gets or sets a boolean indicating whether response type permissions should be ignored. + /// Setting this property to is NOT recommended. + /// + public bool IgnoreResponseTypePermissions { get; set; } + + /// + /// Gets or sets a boolean indicating whether scope permissions should be ignored. + /// Setting this property to is NOT recommended. + /// + public bool IgnoreScopePermissions { get; set; } + + /// + /// Gets the OAuth 2.0/OpenID Connect scopes enabled for this application. + /// + public HashSet Scopes { get; } = new(StringComparer.Ordinal) + { + OpenIddictConstants.Scopes.OpenId + }; + + /// + /// Gets or sets a boolean indicating whether reference access tokens should be used. + /// When set to , the token payload is stored in the database + /// and a crypto-secure random identifier is returned to the client application. + /// Enabling this option is useful when storing a very large number of claims + /// in the tokens, but it is RECOMMENDED to enable column encryption + /// in the database or use the ASP.NET Core Data Protection integration, + /// that provides additional protection against token leakage. + /// + public bool UseReferenceAccessTokens { get; set; } + + /// + /// Gets or sets a boolean indicating whether reference refresh tokens should be used. + /// When set to , the token payload is stored in the database + /// and a crypto-secure random identifier is returned to the client application. + /// Enabling this option is useful when storing a very large number of claims + /// in the tokens, but it is RECOMMENDED to enable column encryption + /// in the database or use the ASP.NET Core Data Protection integration, + /// that provides additional protection against token leakage. + /// + public bool UseReferenceRefreshTokens { get; set; } } diff --git a/src/OpenIddict.Server/OpenIddictServerTransaction.cs b/src/OpenIddict.Server/OpenIddictServerTransaction.cs index f71ff732..66dc976c 100644 --- a/src/OpenIddict.Server/OpenIddictServerTransaction.cs +++ b/src/OpenIddict.Server/OpenIddictServerTransaction.cs @@ -9,46 +9,45 @@ using System.Collections.Generic; using Microsoft.Extensions.Logging; using OpenIddict.Abstractions; -namespace OpenIddict.Server +namespace OpenIddict.Server; + +/// +/// Represents the context associated with an OpenID Connect server request. +/// +public class OpenIddictServerTransaction { /// - /// Represents the context associated with an OpenID Connect server request. + /// Gets or sets the type of the endpoint processing the current request. + /// + public OpenIddictServerEndpointType EndpointType { get; set; } + + /// + /// Gets or sets the issuer address associated with the current transaction, if available. + /// + public Uri? Issuer { get; set; } + + /// + /// Gets or sets the logger associated with the current request. + /// + public ILogger Logger { get; set; } = default!; + + /// + /// Gets or sets the options associated with the current request. + /// + public OpenIddictServerOptions Options { get; set; } = default!; + + /// + /// Gets the additional properties associated with the current request. + /// + public Dictionary Properties { get; } = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets or sets the current OpenID Connect request. + /// + public OpenIddictRequest? Request { get; set; } + + /// + /// Gets or sets the current OpenID Connect response being returned. /// - public class OpenIddictServerTransaction - { - /// - /// Gets or sets the type of the endpoint processing the current request. - /// - public OpenIddictServerEndpointType EndpointType { get; set; } - - /// - /// Gets or sets the issuer address associated with the current transaction, if available. - /// - public Uri? Issuer { get; set; } - - /// - /// Gets or sets the logger associated with the current request. - /// - public ILogger Logger { get; set; } = default!; - - /// - /// Gets or sets the options associated with the current request. - /// - public OpenIddictServerOptions Options { get; set; } = default!; - - /// - /// Gets the additional properties associated with the current request. - /// - public Dictionary Properties { get; } = new(StringComparer.OrdinalIgnoreCase); - - /// - /// Gets or sets the current OpenID Connect request. - /// - public OpenIddictRequest? Request { get; set; } - - /// - /// Gets or sets the current OpenID Connect response being returned. - /// - public OpenIddictResponse? Response { get; set; } - } + public OpenIddictResponse? Response { get; set; } } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs index f17ae825..01e3659e 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreBuilder.cs @@ -9,70 +9,69 @@ using System.ComponentModel; using OpenIddict.Validation.AspNetCore; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure +/// the OpenIddict validation ASP.NET Core integration. +/// +public class OpenIddictValidationAspNetCoreBuilder { /// - /// Exposes the necessary methods required to configure - /// the OpenIddict validation ASP.NET Core integration. + /// Initializes a new instance of . /// - public class OpenIddictValidationAspNetCoreBuilder - { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictValidationAspNetCoreBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); + /// The services collection. + public OpenIddictValidationAspNetCoreBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } - /// - /// Amends the default OpenIddict validation ASP.NET Core configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictValidationAspNetCoreBuilder Configure(Action configuration) + /// + /// Amends the default OpenIddict validation ASP.NET Core configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictValidationAspNetCoreBuilder Configure(Action configuration) + { + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Sets the realm returned to the caller as part of the WWW-Authenticate header. - /// - /// The issuer address. - /// The . - public OpenIddictValidationAspNetCoreBuilder SetRealm(string realm) + /// + /// Sets the realm returned to the caller as part of the WWW-Authenticate header. + /// + /// The issuer address. + /// The . + public OpenIddictValidationAspNetCoreBuilder SetRealm(string realm) + { + if (string.IsNullOrEmpty(realm)) { - if (string.IsNullOrEmpty(realm)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0107), nameof(realm)); - } - - return Configure(options => options.Realm = realm); + throw new ArgumentException(SR.GetResourceString(SR.ID0107), nameof(realm)); } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + return Configure(options => options.Realm = realm); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); - } + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs index 08de15fd..df09c0dd 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs @@ -10,76 +10,75 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.AspNetCore +namespace OpenIddict.Validation.AspNetCore; + +/// +/// Contains the methods required to ensure that the OpenIddict validation configuration is valid. +/// +public class OpenIddictValidationAspNetCoreConfiguration : IConfigureOptions, + IConfigureOptions, + IPostConfigureOptions { /// - /// Contains the methods required to ensure that the OpenIddict validation configuration is valid. + /// Registers the OpenIddict validation handler in the global authentication options. /// - public class OpenIddictValidationAspNetCoreConfiguration : IConfigureOptions, - IConfigureOptions, - IPostConfigureOptions + /// The options instance to initialize. + public void Configure(AuthenticationOptions options) { - /// - /// Registers the OpenIddict validation handler in the global authentication options. - /// - /// The options instance to initialize. - public void Configure(AuthenticationOptions options) + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - // If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception. - if (options.SchemeMap.TryGetValue(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, out var builder) && - builder.HandlerType != typeof(OpenIddictValidationAspNetCoreHandler)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0164)); - } + throw new ArgumentNullException(nameof(options)); + } - options.AddScheme( - OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, displayName: null); + // If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception. + if (options.SchemeMap.TryGetValue(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, out var builder) && + builder.HandlerType != typeof(OpenIddictValidationAspNetCoreHandler)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0164)); } - public void Configure(OpenIddictValidationOptions options) + options.AddScheme( + OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, displayName: null); + } + + public void Configure(OpenIddictValidationOptions options) + { + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new ArgumentNullException(nameof(options)); + } - // Register the built-in event handlers used by the OpenIddict ASP.NET Core validation components. - options.Handlers.AddRange(OpenIddictValidationAspNetCoreHandlers.DefaultHandlers); + // Register the built-in event handlers used by the OpenIddict ASP.NET Core validation components. + options.Handlers.AddRange(OpenIddictValidationAspNetCoreHandlers.DefaultHandlers); + } + + /// + /// Ensures that the authentication configuration is in a consistent and valid state. + /// + /// The name of the options instance to configure, if applicable. + /// The options instance to initialize. + public void PostConfigure(string name, AuthenticationOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); } - /// - /// Ensures that the authentication configuration is in a consistent and valid state. - /// - /// The name of the options instance to configure, if applicable. - /// The options instance to initialize. - public void PostConfigure(string name, AuthenticationOptions options) + if (!TryValidate(options.SchemeMap, options.DefaultSignInScheme) || + !TryValidate(options.SchemeMap, options.DefaultSignOutScheme)) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0165)); + } - if (!TryValidate(options.SchemeMap, options.DefaultSignInScheme) || - !TryValidate(options.SchemeMap, options.DefaultSignOutScheme)) + static bool TryValidate(IDictionary map, string? scheme) + { + // If the scheme was not set or if it cannot be found in the map, return true. + if (string.IsNullOrEmpty(scheme) || !map.TryGetValue(scheme, out var builder)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0165)); + return true; } - static bool TryValidate(IDictionary map, string? scheme) - { - // If the scheme was not set or if it cannot be found in the map, return true. - if (string.IsNullOrEmpty(scheme) || !map.TryGetValue(scheme, out var builder)) - { - return true; - } - - return builder.HandlerType != typeof(OpenIddictValidationAspNetCoreHandler); - } + return builder.HandlerType != typeof(OpenIddictValidationAspNetCoreHandler); } } } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConstants.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConstants.cs index 5cd67d27..a1a4a8ef 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConstants.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConstants.cs @@ -4,26 +4,25 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Validation.AspNetCore +namespace OpenIddict.Validation.AspNetCore; + +/// +/// Exposes common constants used by the OpenIddict ASP.NET Core host. +/// +public static class OpenIddictValidationAspNetCoreConstants { - /// - /// Exposes common constants used by the OpenIddict ASP.NET Core host. - /// - public static class OpenIddictValidationAspNetCoreConstants + public static class Cache { - public static class Cache - { - public const string AuthorizationRequest = "openiddict-authorization-request:"; - public const string LogoutRequest = "openiddict-logout-request:"; - } + public const string AuthorizationRequest = "openiddict-authorization-request:"; + public const string LogoutRequest = "openiddict-logout-request:"; + } - public static class Properties - { - public const string AccessTokenPrincipal = ".access_token_principal"; - public const string Error = ".error"; - public const string ErrorDescription = ".error_description"; - public const string ErrorUri = ".error_uri"; - public const string Scope = ".scope"; - } + public static class Properties + { + public const string AccessTokenPrincipal = ".access_token_principal"; + public const string Error = ".error"; + public const string ErrorDescription = ".error_description"; + public const string ErrorUri = ".error_uri"; + public const string Scope = ".scope"; } } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreDefaults.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreDefaults.cs index f2857a0b..94f2b515 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreDefaults.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreDefaults.cs @@ -6,16 +6,15 @@ using Microsoft.AspNetCore.Authentication; -namespace OpenIddict.Validation.AspNetCore +namespace OpenIddict.Validation.AspNetCore; + +/// +/// Exposes the default values used by the OpenIddict validation handler. +/// +public static class OpenIddictValidationAspNetCoreDefaults { /// - /// Exposes the default values used by the OpenIddict validation handler. + /// Default value for . /// - public static class OpenIddictValidationAspNetCoreDefaults - { - /// - /// Default value for . - /// - public const string AuthenticationScheme = "OpenIddict.Validation.AspNetCore"; - } + public const string AuthenticationScheme = "OpenIddict.Validation.AspNetCore"; } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs index bfd3ee93..73ce5c86 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreExtensions.cs @@ -14,73 +14,72 @@ using OpenIddict.Validation.AspNetCore; using static OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlerFilters; using static OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict validation services. +/// +public static class OpenIddictValidationAspNetCoreExtensions { /// - /// Exposes extensions allowing to register the OpenIddict validation services. + /// Registers the OpenIddict validation services for ASP.NET Core in the DI container. /// - public static class OpenIddictValidationAspNetCoreExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationAspNetCoreBuilder UseAspNetCore(this OpenIddictValidationBuilder builder) { - /// - /// Registers the OpenIddict validation services for ASP.NET Core in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationAspNetCoreBuilder UseAspNetCore(this OpenIddictValidationBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddAuthentication(); + builder.Services.AddAuthentication(); - builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); - // Register the built-in event handlers used by the OpenIddict ASP.NET Core validation components. - // Note: the order used here is not important, as the actual order is set in the options. - builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); + // Register the built-in event handlers used by the OpenIddict ASP.NET Core validation components. + // Note: the order used here is not important, as the actual order is set in the options. + builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); - // Register the built-in filters used by the default OpenIddict ASP.NET Core validation event handlers. - builder.Services.TryAddSingleton(); + // Register the built-in filters used by the default OpenIddict ASP.NET Core validation event handlers. + builder.Services.TryAddSingleton(); - // Register the option initializer used by the OpenIddict ASP.NET Core validation integration services. - // Note: TryAddEnumerable() is used here to ensure the initializers are only registered once. - builder.Services.TryAddEnumerable(new[] - { - ServiceDescriptor.Singleton, OpenIddictValidationAspNetCoreConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictValidationAspNetCoreConfiguration>(), + // Register the option initializer used by the OpenIddict ASP.NET Core validation integration services. + // Note: TryAddEnumerable() is used here to ensure the initializers are only registered once. + builder.Services.TryAddEnumerable(new[] + { + ServiceDescriptor.Singleton, OpenIddictValidationAspNetCoreConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictValidationAspNetCoreConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictValidationAspNetCoreConfiguration>() - }); + ServiceDescriptor.Singleton, OpenIddictValidationAspNetCoreConfiguration>() + }); - return new OpenIddictValidationAspNetCoreBuilder(builder.Services); - } + return new OpenIddictValidationAspNetCoreBuilder(builder.Services); + } - /// - /// Registers the OpenIddict validation services for ASP.NET Core in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the validation services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationBuilder UseAspNetCore( - this OpenIddictValidationBuilder builder, Action configuration) + /// + /// Registers the OpenIddict validation services for ASP.NET Core in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the validation services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationBuilder UseAspNetCore( + this OpenIddictValidationBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseAspNetCore()); + configuration(builder.UseAspNetCore()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreFeature.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreFeature.cs index bcb99ac7..5a56f3a5 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreFeature.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreFeature.cs @@ -4,17 +4,16 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Validation.AspNetCore +namespace OpenIddict.Validation.AspNetCore; + +/// +/// Exposes the current validation transaction to the ASP.NET Core host. +/// +public class OpenIddictValidationAspNetCoreFeature { /// - /// Exposes the current validation transaction to the ASP.NET Core host. + /// Gets or sets the validation transaction that encapsulates all specific + /// information about an individual OpenID Connect validation request. /// - public class OpenIddictValidationAspNetCoreFeature - { - /// - /// Gets or sets the validation transaction that encapsulates all specific - /// information about an individual OpenID Connect validation request. - /// - public OpenIddictValidationTransaction? Transaction { get; set; } - } + public OpenIddictValidationTransaction? Transaction { get; set; } } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs index 672c9e2b..9e3f4238 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs @@ -18,230 +18,229 @@ using static OpenIddict.Validation.OpenIddictValidationEvents; using Properties = OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.AspNetCore +namespace OpenIddict.Validation.AspNetCore; + +/// +/// Provides the logic necessary to extract, validate and handle OpenID Connect requests. +/// +public class OpenIddictValidationAspNetCoreHandler : AuthenticationHandler, + IAuthenticationRequestHandler { + private readonly IOpenIddictValidationDispatcher _dispatcher; + private readonly IOpenIddictValidationFactory _factory; + /// - /// Provides the logic necessary to extract, validate and handle OpenID Connect requests. + /// Creates a new instance of the class. /// - public class OpenIddictValidationAspNetCoreHandler : AuthenticationHandler, - IAuthenticationRequestHandler + public OpenIddictValidationAspNetCoreHandler( + IOpenIddictValidationDispatcher dispatcher, + IOpenIddictValidationFactory factory, + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock) + : base(options, logger, encoder, clock) + { + _dispatcher = dispatcher; + _factory = factory; + } + + /// + public async Task HandleRequestAsync() { - private readonly IOpenIddictValidationDispatcher _dispatcher; - private readonly IOpenIddictValidationFactory _factory; - - /// - /// Creates a new instance of the class. - /// - public OpenIddictValidationAspNetCoreHandler( - IOpenIddictValidationDispatcher dispatcher, - IOpenIddictValidationFactory factory, - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder, - ISystemClock clock) - : base(options, logger, encoder, clock) + // Note: the transaction may be already attached when replaying an ASP.NET Core request + // (e.g when using the built-in status code pages middleware with the re-execute mode). + var transaction = Context.Features.Get()?.Transaction; + if (transaction is null) { - _dispatcher = dispatcher; - _factory = factory; + // Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers. + transaction = await _factory.CreateTransactionAsync(); + transaction.Properties[typeof(HttpRequest).FullName!] = new WeakReference(Request); + + // Attach the OpenIddict validation transaction to the ASP.NET Core features + // so that it can retrieved while performing challenge/forbid operations. + Context.Features.Set(new OpenIddictValidationAspNetCoreFeature { Transaction = transaction }); } - /// - public async Task HandleRequestAsync() + var context = new ProcessRequestContext(transaction); + await _dispatcher.DispatchAsync(context); + + if (context.IsRequestHandled) { - // Note: the transaction may be already attached when replaying an ASP.NET Core request - // (e.g when using the built-in status code pages middleware with the re-execute mode). - var transaction = Context.Features.Get()?.Transaction; - if (transaction is null) - { - // Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers. - transaction = await _factory.CreateTransactionAsync(); - transaction.Properties[typeof(HttpRequest).FullName!] = new WeakReference(Request); + return true; + } - // Attach the OpenIddict validation transaction to the ASP.NET Core features - // so that it can retrieved while performing challenge/forbid operations. - Context.Features.Set(new OpenIddictValidationAspNetCoreFeature { Transaction = transaction }); - } + else if (context.IsRequestSkipped) + { + return false; + } - var context = new ProcessRequestContext(transaction); - await _dispatcher.DispatchAsync(context); + else if (context.IsRejected) + { + var notification = new ProcessErrorContext(transaction) + { + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, + Response = new OpenIddictResponse() + }; - if (context.IsRequestHandled) + await _dispatcher.DispatchAsync(notification); + + if (notification.IsRequestHandled) { return true; } - else if (context.IsRequestSkipped) + else if (notification.IsRequestSkipped) { return false; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); + } - await _dispatcher.DispatchAsync(notification); + return false; + } - if (notification.IsRequestHandled) - { - return true; - } + /// + protected override async Task HandleAuthenticateAsync() + { + var transaction = Context.Features.Get()?.Transaction ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); + + // Note: in many cases, the authentication token was already validated by the time this action is called + // (generally later in the pipeline, when using the pass-through mode). To avoid having to re-validate it, + // the authentication context is resolved from the transaction. If it's not available, a new one is created. + var context = transaction.GetProperty(typeof(ProcessAuthenticationContext).FullName!); + if (context is null) + { + context = new ProcessAuthenticationContext(transaction); + await _dispatcher.DispatchAsync(context); - else if (notification.IsRequestSkipped) - { - return false; - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the authentication result without triggering a new authentication flow. + transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, context); + } + + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return AuthenticateResult.NoResult(); + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); + else if (context.IsRejected) + { + // Note: the missing_token error is special-cased to indicate to ASP.NET Core + // that no authentication result could be produced due to the lack of token. + // This also helps reducing the logging noise when no token is specified. + if (string.Equals(context.Error, Errors.MissingToken, StringComparison.Ordinal)) + { + return AuthenticateResult.NoResult(); } - return false; + var properties = new AuthenticationProperties(new Dictionary + { + [Properties.Error] = context.Error, + [Properties.ErrorDescription] = context.ErrorDescription, + [Properties.ErrorUri] = context.ErrorUri + }); + + return AuthenticateResult.Fail(SR.GetResourceString(SR.ID0113), properties); } - /// - protected override async Task HandleAuthenticateAsync() + else { - var transaction = Context.Features.Get()?.Transaction ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); - - // Note: in many cases, the authentication token was already validated by the time this action is called - // (generally later in the pipeline, when using the pass-through mode). To avoid having to re-validate it, - // the authentication context is resolved from the transaction. If it's not available, a new one is created. - var context = transaction.GetProperty(typeof(ProcessAuthenticationContext).FullName!); - if (context is null) + // A single main claims-based principal instance can be attached to an authentication ticket. + // To return the most appropriate one, the principal is selected based on the endpoint type. + // Independently of the selected main principal, all principals resolved from validated tokens + // are attached to the authentication properties bag so they can be accessed from user code. + var principal = context.EndpointType switch { - context = new ProcessAuthenticationContext(transaction); - await _dispatcher.DispatchAsync(context); + OpenIddictValidationEndpointType.Unknown => context.AccessTokenPrincipal, - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the authentication result without triggering a new authentication flow. - transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, context); - } + _ => null + }; - if (context.IsRequestHandled || context.IsRequestSkipped) + if (principal is null) { return AuthenticateResult.NoResult(); } - else if (context.IsRejected) + var properties = new AuthenticationProperties { - // Note: the missing_token error is special-cased to indicate to ASP.NET Core - // that no authentication result could be produced due to the lack of token. - // This also helps reducing the logging noise when no token is specified. - if (string.Equals(context.Error, Errors.MissingToken, StringComparison.Ordinal)) - { - return AuthenticateResult.NoResult(); - } + ExpiresUtc = principal.GetExpirationDate(), + IssuedUtc = principal.GetCreationDate() + }; - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); + List? tokens = null; - return AuthenticateResult.Fail(SR.GetResourceString(SR.ID0113), properties); - } + // Attach the tokens to allow any ASP.NET Core component (e.g a controller) + // to retrieve them (e.g to make an API request to another application). - else + if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken)) { - // A single main claims-based principal instance can be attached to an authentication ticket. - // To return the most appropriate one, the principal is selected based on the endpoint type. - // Independently of the selected main principal, all principals resolved from validated tokens - // are attached to the authentication properties bag so they can be accessed from user code. - var principal = context.EndpointType switch + tokens ??= new(capacity: 1); + tokens.Add(new AuthenticationToken { - OpenIddictValidationEndpointType.Unknown => context.AccessTokenPrincipal, - - _ => null - }; + Name = TokenTypeHints.AccessToken, + Value = context.AccessToken + }); - if (principal is null) - { - return AuthenticateResult.NoResult(); - } + properties.SetParameter(Properties.AccessTokenPrincipal, context.AccessTokenPrincipal); + } - var properties = new AuthenticationProperties - { - ExpiresUtc = principal.GetExpirationDate(), - IssuedUtc = principal.GetCreationDate() - }; + if (tokens is { Count: > 0 }) + { + properties.StoreTokens(tokens); + } - List? tokens = null; + return AuthenticateResult.Success(new AuthenticationTicket(principal, properties, + OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)); + } + } - // Attach the tokens to allow any ASP.NET Core component (e.g a controller) - // to retrieve them (e.g to make an API request to another application). + /// + protected override async Task HandleChallengeAsync(AuthenticationProperties? properties) + { + var transaction = Context.Features.Get()?.Transaction ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); - if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken)) - { - tokens ??= new(capacity: 1); - tokens.Add(new AuthenticationToken - { - Name = TokenTypeHints.AccessToken, - Value = context.AccessToken - }); + transaction.Properties[typeof(AuthenticationProperties).FullName!] = properties ?? new AuthenticationProperties(); - properties.SetParameter(Properties.AccessTokenPrincipal, context.AccessTokenPrincipal); - } + var context = new ProcessChallengeContext(transaction) + { + Response = new OpenIddictResponse() + }; - if (tokens is { Count: > 0 }) - { - properties.StoreTokens(tokens); - } + await _dispatcher.DispatchAsync(context); - return AuthenticateResult.Success(new AuthenticationTicket(principal, properties, - OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)); - } + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return; } - /// - protected override async Task HandleChallengeAsync(AuthenticationProperties? properties) + else if (context.IsRejected) { - var transaction = Context.Features.Get()?.Transaction ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); - - transaction.Properties[typeof(AuthenticationProperties).FullName!] = properties ?? new AuthenticationProperties(); - - var context = new ProcessChallengeContext(transaction) + var notification = new ProcessErrorContext(transaction) { + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, Response = new OpenIddictResponse() }; - await _dispatcher.DispatchAsync(context); + await _dispatcher.DispatchAsync(notification); - if (context.IsRequestHandled || context.IsRequestSkipped) + if (notification.IsRequestHandled || context.IsRequestSkipped) { return; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; - - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled || context.IsRequestSkipped) - { - return; - } - - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); } - - /// - protected override Task HandleForbiddenAsync(AuthenticationProperties? properties) - => HandleChallengeAsync(properties); } + + /// + protected override Task HandleForbiddenAsync(AuthenticationProperties? properties) + => HandleChallengeAsync(properties); } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlerFilters.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlerFilters.cs index a8045918..4e5f81e0 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlerFilters.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlerFilters.cs @@ -10,28 +10,27 @@ using System.Threading.Tasks; using Microsoft.AspNetCore; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace OpenIddict.Validation.AspNetCore +namespace OpenIddict.Validation.AspNetCore; + +/// +/// Contains a collection of event handler filters commonly used by the ASP.NET Core handlers. +/// +[EditorBrowsable(EditorBrowsableState.Advanced)] +public static class OpenIddictValidationAspNetCoreHandlerFilters { /// - /// Contains a collection of event handler filters commonly used by the ASP.NET Core handlers. + /// Represents a filter that excludes the associated handlers if no ASP.NET Core request can be found. /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static class OpenIddictValidationAspNetCoreHandlerFilters + public class RequireHttpRequest : IOpenIddictValidationHandlerFilter { - /// - /// Represents a filter that excludes the associated handlers if no ASP.NET Core request can be found. - /// - public class RequireHttpRequest : IOpenIddictValidationHandlerFilter + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Transaction.GetHttpRequest() is not null); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Transaction.GetHttpRequest() is not null); } } } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs index 341c00a3..ba706c5f 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs @@ -30,654 +30,653 @@ using static OpenIddict.Validation.OpenIddictValidationHandlers; using Properties = OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.AspNetCore +namespace OpenIddict.Validation.AspNetCore; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class OpenIddictValidationAspNetCoreHandlers { - [EditorBrowsable(EditorBrowsableState.Never)] - public static partial class OpenIddictValidationAspNetCoreHandlers + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Request top-level processing: + */ + InferIssuerFromHost.Descriptor, + + /* + * Authentication processing: + */ + ExtractAccessTokenFromAuthorizationHeader.Descriptor, + ExtractAccessTokenFromBodyForm.Descriptor, + ExtractAccessTokenFromQueryString.Descriptor, + + /* + * Challenge processing: + */ + AttachHostChallengeError.Descriptor, + + /* + * Response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessChallengeErrorResponse.Descriptor, + ProcessJsonResponse.Descriptor, + + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessChallengeErrorResponse.Descriptor, + ProcessJsonResponse.Descriptor); + + /// + /// Contains the logic responsible of infering the default issuer from the HTTP request host and validating it. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class InferIssuerFromHost : IOpenIddictValidationHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Request top-level processing: - */ - InferIssuerFromHost.Descriptor, - - /* - * Authentication processing: - */ - ExtractAccessTokenFromAuthorizationHeader.Descriptor, - ExtractAccessTokenFromBodyForm.Descriptor, - ExtractAccessTokenFromQueryString.Descriptor, - - /* - * Challenge processing: - */ - AttachHostChallengeError.Descriptor, - - /* - * Response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessChallengeErrorResponse.Descriptor, - ProcessJsonResponse.Descriptor, - - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessChallengeErrorResponse.Descriptor, - ProcessJsonResponse.Descriptor); - /// - /// Contains the logic responsible of infering the default issuer from the HTTP request host and validating it. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class InferIssuerFromHost : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - // Only use the current host as the issuer if the - // issuer was not explicitly set in the options. - if (context.Issuer is not null) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (!request.Host.HasValue) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2081(HeaderNames.Host), - uri: SR.FormatID8000(SR.ID2081)); + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - return default; - } + // Only use the current host as the issuer if the + // issuer was not explicitly set in the options. + if (context.Issuer is not null) + { + return default; + } - if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase, UriKind.Absolute, out Uri? issuer) || - !issuer.IsWellFormedOriginalString()) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2082(HeaderNames.Host), - uri: SR.FormatID8000(SR.ID2082)); + if (!request.Host.HasValue) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2081(HeaderNames.Host), + uri: SR.FormatID8000(SR.ID2081)); - return default; - } + return default; + } - context.Issuer = issuer; + if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase, UriKind.Absolute, out Uri? issuer) || + !issuer.IsWellFormedOriginalString()) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2082(HeaderNames.Host), + uri: SR.FormatID8000(SR.ID2082)); return default; } + + context.Issuer = issuer; + + return default; } + } + /// + /// Contains the logic responsible of extracting the access token from the standard HTTP Authorization header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ExtractAccessTokenFromAuthorizationHeader : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the access token from the standard HTTP Authorization header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractAccessTokenFromAuthorizationHeader : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // If a token was already resolved, don't overwrite it. - if (!string.IsNullOrEmpty(context.AccessToken)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // If a token was already resolved, don't overwrite it. + if (!string.IsNullOrEmpty(context.AccessToken)) + { + return default; + } - // Resolve the access token from the standard Authorization header. - // See https://tools.ietf.org/html/rfc6750#section-2.1 for more information. - string header = request.Headers[HeaderNames.Authorization]; - if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) - { - context.AccessToken = header.Substring("Bearer ".Length); + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - return default; - } + // Resolve the access token from the standard Authorization header. + // See https://tools.ietf.org/html/rfc6750#section-2.1 for more information. + string header = request.Headers[HeaderNames.Authorization]; + if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { + context.AccessToken = header.Substring("Bearer ".Length); return default; } + + return default; } + } + /// + /// Contains the logic responsible of extracting the access token from the standard access_token form parameter. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ExtractAccessTokenFromBodyForm : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the access token from the standard access_token form parameter. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractAccessTokenFromBodyForm : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ExtractAccessTokenFromAuthorizationHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ExtractAccessTokenFromAuthorizationHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If a token was already resolved, don't overwrite it. - if (!string.IsNullOrEmpty(context.AccessToken)) - { - return; - } + // If a token was already resolved, don't overwrite it. + if (!string.IsNullOrEmpty(context.AccessToken)) + { + return; + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - if (string.IsNullOrEmpty(request.ContentType) || - !request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) - { - return; - } + if (string.IsNullOrEmpty(request.ContentType) || + !request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) + { + return; + } - // Resolve the access token from the standard access_token form parameter. - // See https://tools.ietf.org/html/rfc6750#section-2.2 for more information. - var form = await request.ReadFormAsync(request.HttpContext.RequestAborted); - if (form.TryGetValue(Parameters.AccessToken, out StringValues token)) - { - context.AccessToken = token; + // Resolve the access token from the standard access_token form parameter. + // See https://tools.ietf.org/html/rfc6750#section-2.2 for more information. + var form = await request.ReadFormAsync(request.HttpContext.RequestAborted); + if (form.TryGetValue(Parameters.AccessToken, out StringValues token)) + { + context.AccessToken = token; - return; - } + return; } } + } + /// + /// Contains the logic responsible of extracting the access token from the standard access_token query parameter. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ExtractAccessTokenFromQueryString : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the access token from the standard access_token query parameter. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractAccessTokenFromQueryString : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ExtractAccessTokenFromBodyForm.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ExtractAccessTokenFromBodyForm.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // If a token was already resolved, don't overwrite it. - if (!string.IsNullOrEmpty(context.AccessToken)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetHttpRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // If a token was already resolved, don't overwrite it. + if (!string.IsNullOrEmpty(context.AccessToken)) + { + return default; + } - // Resolve the access token from the standard access_token query parameter. - // See https://tools.ietf.org/html/rfc6750#section-2.3 for more information. - if (request.Query.TryGetValue(Parameters.AccessToken, out StringValues token)) - { - context.AccessToken = token; + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetHttpRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - return default; - } + // Resolve the access token from the standard access_token query parameter. + // See https://tools.ietf.org/html/rfc6750#section-2.3 for more information. + if (request.Query.TryGetValue(Parameters.AccessToken, out StringValues token)) + { + context.AccessToken = token; return default; } + + return default; } + } + /// + /// Contains the logic responsible of attaching the error details using the ASP.NET Core authentication properties. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class AttachHostChallengeError : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of attaching the error details using the ASP.NET Core authentication properties. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachHostChallengeError : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 50_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 50_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessChallengeContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - return default; - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is null) + { + return default; + } - if (properties.Items.TryGetValue(Properties.Error, out string? error) && - !string.IsNullOrEmpty(error)) - { - context.Parameters[Parameters.Error] = error; - } + if (properties.Items.TryGetValue(Properties.Error, out string? error) && + !string.IsNullOrEmpty(error)) + { + context.Parameters[Parameters.Error] = error; + } - if (properties.Items.TryGetValue(Properties.ErrorDescription, out string? description) && - !string.IsNullOrEmpty(description)) - { - context.Parameters[Parameters.ErrorDescription] = description; - } + if (properties.Items.TryGetValue(Properties.ErrorDescription, out string? description) && + !string.IsNullOrEmpty(description)) + { + context.Parameters[Parameters.ErrorDescription] = description; + } - if (properties.Items.TryGetValue(Properties.ErrorUri, out string? uri) && - !string.IsNullOrEmpty(uri)) - { - context.Parameters[Parameters.ErrorUri] = uri; - } + if (properties.Items.TryGetValue(Properties.ErrorUri, out string? uri) && + !string.IsNullOrEmpty(uri)) + { + context.Parameters[Parameters.ErrorUri] = uri; + } - if (properties.Items.TryGetValue(Properties.Scope, out string? scope) && - !string.IsNullOrEmpty(scope)) - { - context.Parameters[Parameters.Scope] = scope; - } + if (properties.Items.TryGetValue(Properties.Scope, out string? scope) && + !string.IsNullOrEmpty(scope)) + { + context.Parameters[Parameters.Scope] = scope; + } - foreach (var parameter in properties.Parameters) + foreach (var parameter in properties.Parameters) + { + context.Parameters[parameter.Key] = parameter.Value switch { - context.Parameters[parameter.Key] = parameter.Value switch - { - OpenIddictParameter value => value, - JsonElement value => new OpenIddictParameter(value), - bool value => new OpenIddictParameter(value), - int value => new OpenIddictParameter(value), - long value => new OpenIddictParameter(value), - string value => new OpenIddictParameter(value), - string[] value => new OpenIddictParameter(value), - - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) - }; - } + OpenIddictParameter value => value, + JsonElement value => new OpenIddictParameter(value), + bool value => new OpenIddictParameter(value), + int value => new OpenIddictParameter(value), + long value => new OpenIddictParameter(value), + string value => new OpenIddictParameter(value), + string[] value => new OpenIddictParameter(value), - return default; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0115)) + }; } + + return default; } + } + /// + /// Contains the logic responsible of attaching an appropriate HTTP status code. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class AttachHttpResponseCode : IOpenIddictValidationHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of attaching an appropriate HTTP status code. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachHttpResponseCode : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - response.StatusCode = context.Transaction.Response.Error switch - { - null => 200, + response.StatusCode = context.Transaction.Response.Error switch + { + null => 200, - Errors.InvalidToken or Errors.MissingToken => 401, + Errors.InvalidToken or Errors.MissingToken => 401, - Errors.InsufficientAccess or Errors.InsufficientScope => 403, + Errors.InsufficientAccess or Errors.InsufficientScope => 403, - _ => 400 - }; + _ => 400 + }; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the appropriate HTTP response cache headers. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class AttachCacheControlHeader : IOpenIddictValidationHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of attaching the appropriate HTTP response cache headers. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachCacheControlHeader : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachHttpResponseCode.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachHttpResponseCode.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - // Prevent the response from being cached. - response.Headers[HeaderNames.CacheControl] = "no-store"; - response.Headers[HeaderNames.Pragma] = "no-cache"; - response.Headers[HeaderNames.Expires] = "Thu, 01 Jan 1970 00:00:00 GMT"; + // Prevent the response from being cached. + response.Headers[HeaderNames.CacheControl] = "no-store"; + response.Headers[HeaderNames.Pragma] = "no-cache"; + response.Headers[HeaderNames.Expires] = "Thu, 01 Jan 1970 00:00:00 GMT"; - return default; - } + return default; } + } + + /// + /// Contains the logic responsible of attaching errors details to the WWW-Authenticate header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class AttachWwwAuthenticateHeader : IOpenIddictValidationHandler where TContext : BaseRequestContext + { + private readonly IOptionsMonitor _options; + + public AttachWwwAuthenticateHeader(IOptionsMonitor options) + => _options = options; /// - /// Contains the logic responsible of attaching errors details to the WWW-Authenticate header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachWwwAuthenticateHeader : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachCacheControlHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - private readonly IOptionsMonitor _options; - - public AttachWwwAuthenticateHeader(IOptionsMonitor options) - => _options = options; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachCacheControlHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - var scheme = context.Transaction.Response.Error switch - { - Errors.InvalidToken or - Errors.MissingToken or - Errors.InsufficientAccess or - Errors.InsufficientScope => Schemes.Bearer, + var scheme = context.Transaction.Response.Error switch + { + Errors.InvalidToken or + Errors.MissingToken or + Errors.InsufficientAccess or + Errors.InsufficientScope => Schemes.Bearer, - _ => null - }; + _ => null + }; - if (string.IsNullOrEmpty(scheme)) - { - return default; - } + if (string.IsNullOrEmpty(scheme)) + { + return default; + } - var parameters = new Dictionary(StringComparer.Ordinal); + var parameters = new Dictionary(StringComparer.Ordinal); - // If a realm was configured in the options, attach it to the parameters. - if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) - { - parameters[Parameters.Realm] = _options.CurrentValue.Realm; - } + // If a realm was configured in the options, attach it to the parameters. + if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) + { + parameters[Parameters.Realm] = _options.CurrentValue.Realm; + } - foreach (var parameter in context.Transaction.Response.GetParameters()) + foreach (var parameter in context.Transaction.Response.GetParameters()) + { + // Note: the error details are only included if the error was not caused by a missing token, as recommended + // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. + if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && + (string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || + string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || + string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) { - // Note: the error details are only included if the error was not caused by a missing token, as recommended - // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. - if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && - (string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || - string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || - string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) - { - continue; - } - - // Ignore values that can't be represented as unique strings. - var value = (string?) parameter.Value; - if (string.IsNullOrEmpty(value)) - { - continue; - } - - parameters[parameter.Key] = value; + continue; } - var builder = new StringBuilder(scheme); - - foreach (var parameter in parameters) + // Ignore values that can't be represented as unique strings. + var value = (string?) parameter.Value; + if (string.IsNullOrEmpty(value)) { - builder.Append(' '); - builder.Append(parameter.Key); - builder.Append('='); - builder.Append('"'); - builder.Append(parameter.Value.Replace("\"", "\\\"")); - builder.Append('"'); - builder.Append(','); + continue; } - // If the WWW-Authenticate header ends with a comma, remove it. - if (builder[builder.Length - 1] == ',') - { - builder.Remove(builder.Length - 1, 1); - } + parameters[parameter.Key] = value; + } - response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString()); + var builder = new StringBuilder(scheme); - return default; + foreach (var parameter in parameters) + { + builder.Append(' '); + builder.Append(parameter.Key); + builder.Append('='); + builder.Append('"'); + builder.Append(parameter.Value.Replace("\"", "\\\"")); + builder.Append('"'); + builder.Append(','); + } + + // If the WWW-Authenticate header ends with a comma, remove it. + if (builder[builder.Length - 1] == ',') + { + builder.Remove(builder.Length - 1, 1); } + + response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString()); + + return default; } + } + /// + /// Contains the logic responsible of processing challenge responses that contain a WWW-Authenticate header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessChallengeErrorResponse : IOpenIddictValidationHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing challenge responses that contain a WWW-Authenticate header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessChallengeErrorResponse : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachWwwAuthenticateHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachWwwAuthenticateHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } - - // If the response doesn't contain a WWW-Authenticate header, don't return an empty response. - if (!response.Headers.ContainsKey(HeaderNames.WWWAuthenticate)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6141), context.Transaction.Response); - context.HandleRequest(); + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } + // If the response doesn't contain a WWW-Authenticate header, don't return an empty response. + if (!response.Headers.ContainsKey(HeaderNames.WWWAuthenticate)) + { return default; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6141), context.Transaction.Response); + context.HandleRequest(); + + return default; } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// + public class ProcessJsonResponse : IOpenIddictValidationHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessJsonResponse : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ProcessChallengeErrorResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ProcessChallengeErrorResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); - } + // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetHttpRequest()?.HttpContext.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response); - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = true - }); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = true + }); - context.Transaction.Response.WriteTo(writer); - writer.Flush(); + context.Transaction.Response.WriteTo(writer); + writer.Flush(); - response.ContentLength = stream.Length; - response.ContentType = "application/json;charset=UTF-8"; + response.ContentLength = stream.Length; + response.ContentType = "application/json;charset=UTF-8"; - stream.Seek(offset: 0, loc: SeekOrigin.Begin); - await stream.CopyToAsync(response.Body, 4096, response.HttpContext.RequestAborted); + stream.Seek(offset: 0, loc: SeekOrigin.Begin); + await stream.CopyToAsync(response.Body, 4096, response.HttpContext.RequestAborted); - context.HandleRequest(); - } + context.HandleRequest(); } } } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHelpers.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHelpers.cs index d4f3f341..4a379cb4 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHelpers.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHelpers.cs @@ -11,81 +11,80 @@ using OpenIddict.Validation; using OpenIddict.Validation.AspNetCore; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace Microsoft.AspNetCore +namespace Microsoft.AspNetCore; + +/// +/// Exposes companion extensions for the OpenIddict/ASP.NET Core integration. +/// +public static class OpenIddictValidationAspNetCoreHelpers { /// - /// Exposes companion extensions for the OpenIddict/ASP.NET Core integration. + /// Retrieves the instance stored in the properties. /// - public static class OpenIddictValidationAspNetCoreHelpers + /// The transaction instance. + /// The instance or null if it couldn't be found. + public static HttpRequest? GetHttpRequest(this OpenIddictValidationTransaction transaction) { - /// - /// Retrieves the instance stored in the properties. - /// - /// The transaction instance. - /// The instance or null if it couldn't be found. - public static HttpRequest? GetHttpRequest(this OpenIddictValidationTransaction transaction) + if (transaction is null) { - if (transaction is null) - { - throw new ArgumentNullException(nameof(transaction)); - } - - if (!transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)) - { - return null; - } - - if (property is WeakReference reference && reference.TryGetTarget(out HttpRequest? request)) - { - return request; - } + throw new ArgumentNullException(nameof(transaction)); + } + if (!transaction.Properties.TryGetValue(typeof(HttpRequest).FullName!, out object? property)) + { return null; } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The . - public static OpenIddictValidationEndpointType GetOpenIddictValidationEndpointType(this HttpContext context) + if (property is WeakReference reference && reference.TryGetTarget(out HttpRequest? request)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return context.Features.Get()?.Transaction?.EndpointType ?? default; + return request; } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The instance or null if it couldn't be found. - public static OpenIddictRequest? GetOpenIddictValidationRequest(this HttpContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return null; + } - return context.Features.Get()?.Transaction?.Request; + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The . + public static OpenIddictValidationEndpointType GetOpenIddictValidationEndpointType(this HttpContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The instance or null if it couldn't be found. - public static OpenIddictResponse? GetOpenIddictValidationResponse(this HttpContext context) + return context.Features.Get()?.Transaction?.EndpointType ?? default; + } + + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The instance or null if it couldn't be found. + public static OpenIddictRequest? GetOpenIddictValidationRequest(this HttpContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } + + return context.Features.Get()?.Transaction?.Request; + } - return context.Features.Get()?.Transaction?.Response; + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The instance or null if it couldn't be found. + public static OpenIddictResponse? GetOpenIddictValidationResponse(this HttpContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); } + + return context.Features.Get()?.Transaction?.Response; } } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreOptions.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreOptions.cs index 3e0625df..4e6a5b37 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreOptions.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreOptions.cs @@ -6,16 +6,15 @@ using Microsoft.AspNetCore.Authentication; -namespace OpenIddict.Validation.AspNetCore +namespace OpenIddict.Validation.AspNetCore; + +/// +/// Provides various settings needed to configure the OpenIddict ASP.NET Core validation integration. +/// +public class OpenIddictValidationAspNetCoreOptions : AuthenticationSchemeOptions { /// - /// Provides various settings needed to configure the OpenIddict ASP.NET Core validation integration. + /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. /// - public class OpenIddictValidationAspNetCoreOptions : AuthenticationSchemeOptions - { - /// - /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. - /// - public string? Realm { get; set; } - } + public string? Realm { get; set; } } diff --git a/src/OpenIddict.Validation.DataProtection/IOpenIddictValidationDataProtectionFormatter.cs b/src/OpenIddict.Validation.DataProtection/IOpenIddictValidationDataProtectionFormatter.cs index 76a59a7b..9cb0f059 100644 --- a/src/OpenIddict.Validation.DataProtection/IOpenIddictValidationDataProtectionFormatter.cs +++ b/src/OpenIddict.Validation.DataProtection/IOpenIddictValidationDataProtectionFormatter.cs @@ -7,10 +7,9 @@ using System.IO; using System.Security.Claims; -namespace OpenIddict.Validation.DataProtection +namespace OpenIddict.Validation.DataProtection; + +public interface IOpenIddictValidationDataProtectionFormatter { - public interface IOpenIddictValidationDataProtectionFormatter - { - ClaimsPrincipal ReadToken(BinaryReader reader); - } -} \ No newline at end of file + ClaimsPrincipal ReadToken(BinaryReader reader); +} diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionBuilder.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionBuilder.cs index 70b5fe79..93abca76 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionBuilder.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionBuilder.cs @@ -9,86 +9,85 @@ using System.ComponentModel; using Microsoft.AspNetCore.DataProtection; using OpenIddict.Validation.DataProtection; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the +/// OpenIddict ASP.NET Core Data Protection integration. +/// +public class OpenIddictValidationDataProtectionBuilder { /// - /// Exposes the necessary methods required to configure the - /// OpenIddict ASP.NET Core Data Protection integration. + /// Initializes a new instance of . /// - public class OpenIddictValidationDataProtectionBuilder - { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictValidationDataProtectionBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); + /// The services collection. + public OpenIddictValidationDataProtectionBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } - /// - /// Amends the default OpenIddict validation ASP.NET Core Data Protection configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictValidationDataProtectionBuilder Configure(Action configuration) + /// + /// Amends the default OpenIddict validation ASP.NET Core Data Protection configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictValidationDataProtectionBuilder Configure(Action configuration) + { + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Configures OpenIddict to use a specific data protection provider - /// instead of relying on the default instance provided by the DI container. - /// - /// The data protection provider used to create token protectors. - /// The . - public OpenIddictValidationDataProtectionBuilder UseDataProtectionProvider(IDataProtectionProvider provider) + /// + /// Configures OpenIddict to use a specific data protection provider + /// instead of relying on the default instance provided by the DI container. + /// + /// The data protection provider used to create token protectors. + /// The . + public OpenIddictValidationDataProtectionBuilder UseDataProtectionProvider(IDataProtectionProvider provider) + { + if (provider is null) { - if (provider is null) - { - throw new ArgumentNullException(nameof(provider)); - } - - return Configure(options => options.DataProtectionProvider = provider); + throw new ArgumentNullException(nameof(provider)); } - /// - /// Configures OpenIddict to use a specific formatter instead of relying on the default instance. - /// - /// The formatter used to read tokens. - /// The . - public OpenIddictValidationDataProtectionBuilder UseFormatter(IOpenIddictValidationDataProtectionFormatter formatter) - { - if (formatter is null) - { - throw new ArgumentNullException(nameof(formatter)); - } + return Configure(options => options.DataProtectionProvider = provider); + } - return Configure(options => options.Formatter = formatter); + /// + /// Configures OpenIddict to use a specific formatter instead of relying on the default instance. + /// + /// The formatter used to read tokens. + /// The . + public OpenIddictValidationDataProtectionBuilder UseFormatter(IOpenIddictValidationDataProtectionFormatter formatter) + { + if (formatter is null) + { + throw new ArgumentNullException(nameof(formatter)); } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + return Configure(options => options.Formatter = formatter); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); - } + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs index f9e99721..17df7ea7 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs @@ -8,48 +8,47 @@ using System; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Options; -namespace OpenIddict.Validation.DataProtection +namespace OpenIddict.Validation.DataProtection; + +/// +/// Contains the methods required to ensure that the OpenIddict ASP.NET Core Data Protection configuration is valid. +/// +public class OpenIddictValidationDataProtectionConfiguration : IConfigureOptions, + IPostConfigureOptions { + private readonly IDataProtectionProvider _dataProtectionProvider; + /// - /// Contains the methods required to ensure that the OpenIddict ASP.NET Core Data Protection configuration is valid. + /// Creates a new instance of the class. /// - public class OpenIddictValidationDataProtectionConfiguration : IConfigureOptions, - IPostConfigureOptions - { - private readonly IDataProtectionProvider _dataProtectionProvider; - - /// - /// Creates a new instance of the class. - /// - /// The ASP.NET Core Data Protection provider. - public OpenIddictValidationDataProtectionConfiguration(IDataProtectionProvider dataProtectionProvider) - => _dataProtectionProvider = dataProtectionProvider; + /// The ASP.NET Core Data Protection provider. + public OpenIddictValidationDataProtectionConfiguration(IDataProtectionProvider dataProtectionProvider) + => _dataProtectionProvider = dataProtectionProvider; - public void Configure(OpenIddictValidationOptions options) + public void Configure(OpenIddictValidationOptions options) + { + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - // Register the built-in event handlers used by the OpenIddict Data Protection validation components. - options.Handlers.AddRange(OpenIddictValidationDataProtectionHandlers.DefaultHandlers); + throw new ArgumentNullException(nameof(options)); } - /// - /// Populates the default OpenIddict ASP.NET Core Data Protection validation options - /// and ensures that the configuration is in a consistent and valid state. - /// - /// The name of the options instance to configure, if applicable. - /// The options instance to initialize. - public void PostConfigure(string name, OpenIddictValidationDataProtectionOptions options) - { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + // Register the built-in event handlers used by the OpenIddict Data Protection validation components. + options.Handlers.AddRange(OpenIddictValidationDataProtectionHandlers.DefaultHandlers); + } - options.DataProtectionProvider ??= _dataProtectionProvider; + /// + /// Populates the default OpenIddict ASP.NET Core Data Protection validation options + /// and ensures that the configuration is in a consistent and valid state. + /// + /// The name of the options instance to configure, if applicable. + /// The options instance to initialize. + public void PostConfigure(string name, OpenIddictValidationDataProtectionOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); } + + options.DataProtectionProvider ??= _dataProtectionProvider; } } diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs index 6b7b54e4..9b6694a5 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs @@ -4,55 +4,54 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Validation.DataProtection +namespace OpenIddict.Validation.DataProtection; + +public static class OpenIddictValidationDataProtectionConstants { - public static class OpenIddictValidationDataProtectionConstants + public static class Properties { - public static class Properties + public const string AccessTokenLifetime = ".access_token_lifetime"; + public const string AuthorizationCodeLifetime = ".authorization_code_lifetime"; + public const string Audiences = ".audiences"; + public const string CodeChallenge = ".code_challenge"; + public const string CodeChallengeMethod = ".code_challenge_method"; + public const string DataProtector = ".data_protector"; + public const string DeviceCodeId = ".device_code_id"; + public const string DeviceCodeLifetime = ".device_code_lifetime"; + public const string Expires = ".expires"; + public const string IdentityTokenLifetime = ".identity_token_lifetime"; + public const string InternalAuthorizationId = ".internal_authorization_id"; + public const string InternalTokenId = ".internal_token_id"; + public const string Issued = ".issued"; + public const string Nonce = ".nonce"; + public const string OriginalRedirectUri = ".original_redirect_uri"; + public const string Presenters = ".presenters"; + public const string RefreshTokenLifetime = ".refresh_token_lifetime"; + public const string Resources = ".resources"; + public const string Scopes = ".scopes"; + public const string UserCodeLifetime = ".user_code_lifetime"; + } + + public static class Purposes + { + public static class Features { - public const string AccessTokenLifetime = ".access_token_lifetime"; - public const string AuthorizationCodeLifetime = ".authorization_code_lifetime"; - public const string Audiences = ".audiences"; - public const string CodeChallenge = ".code_challenge"; - public const string CodeChallengeMethod = ".code_challenge_method"; - public const string DataProtector = ".data_protector"; - public const string DeviceCodeId = ".device_code_id"; - public const string DeviceCodeLifetime = ".device_code_lifetime"; - public const string Expires = ".expires"; - public const string IdentityTokenLifetime = ".identity_token_lifetime"; - public const string InternalAuthorizationId = ".internal_authorization_id"; - public const string InternalTokenId = ".internal_token_id"; - public const string Issued = ".issued"; - public const string Nonce = ".nonce"; - public const string OriginalRedirectUri = ".original_redirect_uri"; - public const string Presenters = ".presenters"; - public const string RefreshTokenLifetime = ".refresh_token_lifetime"; - public const string Resources = ".resources"; - public const string Scopes = ".scopes"; - public const string UserCodeLifetime = ".user_code_lifetime"; + public const string ReferenceTokens = "UseReferenceTokens"; } - public static class Purposes + public static class Formats { - public static class Features - { - public const string ReferenceTokens = "UseReferenceTokens"; - } - - public static class Formats - { - public const string AccessToken = "AccessTokenFormat"; - } + public const string AccessToken = "AccessTokenFormat"; + } - public static class Handlers - { - public const string Server = "OpenIdConnectServerHandler"; - } + public static class Handlers + { + public const string Server = "OpenIdConnectServerHandler"; + } - public static class Schemes - { - public const string Server = "ASOS"; - } + public static class Schemes + { + public const string Server = "ASOS"; } } } diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionExtensions.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionExtensions.cs index 532551fc..c002c46d 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionExtensions.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionExtensions.cs @@ -12,65 +12,64 @@ using OpenIddict.Validation; using OpenIddict.Validation.DataProtection; using static OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionHandlers; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict ASP.NET Core Data Protection validation services. +/// +public static class OpenIddictValidationDataProtectionExtensions { /// - /// Exposes extensions allowing to register the OpenIddict ASP.NET Core Data Protection validation services. + /// Registers the OpenIddict ASP.NET Core Data Protection validation services in the DI container. /// - public static class OpenIddictValidationDataProtectionExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationDataProtectionBuilder UseDataProtection(this OpenIddictValidationBuilder builder) { - /// - /// Registers the OpenIddict ASP.NET Core Data Protection validation services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationDataProtectionBuilder UseDataProtection(this OpenIddictValidationBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddDataProtection(); + builder.Services.AddDataProtection(); - // Register the built-in validation event handlers used by the OpenIddict Data Protection components. - // Note: the order used here is not important, as the actual order is set in the options. - builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); + // Register the built-in validation event handlers used by the OpenIddict Data Protection components. + // Note: the order used here is not important, as the actual order is set in the options. + builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); - // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. - builder.Services.TryAddEnumerable(new[] - { - ServiceDescriptor.Singleton, OpenIddictValidationDataProtectionConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictValidationDataProtectionConfiguration>() - }); + // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. + builder.Services.TryAddEnumerable(new[] + { + ServiceDescriptor.Singleton, OpenIddictValidationDataProtectionConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictValidationDataProtectionConfiguration>() + }); - return new OpenIddictValidationDataProtectionBuilder(builder.Services); - } + return new OpenIddictValidationDataProtectionBuilder(builder.Services); + } - /// - /// Registers the OpenIddict ASP.NET Core Data Protection validation services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the validation services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationBuilder UseDataProtection( - this OpenIddictValidationBuilder builder, Action configuration) + /// + /// Registers the OpenIddict ASP.NET Core Data Protection validation services in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the validation services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationBuilder UseDataProtection( + this OpenIddictValidationBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseDataProtection()); + configuration(builder.UseDataProtection()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs index 7e9a199e..d913ea8b 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs @@ -15,184 +15,183 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using Properties = OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.DataProtection +namespace OpenIddict.Validation.DataProtection; + +public class OpenIddictValidationDataProtectionFormatter : IOpenIddictValidationDataProtectionFormatter { - public class OpenIddictValidationDataProtectionFormatter : IOpenIddictValidationDataProtectionFormatter + public ClaimsPrincipal ReadToken(BinaryReader reader) { - public ClaimsPrincipal ReadToken(BinaryReader reader) + if (reader is null) { - if (reader is null) - { - throw new ArgumentNullException(nameof(reader)); - } + throw new ArgumentNullException(nameof(reader)); + } - var (principal, properties) = Read(reader); - - // Tokens serialized using the ASP.NET Core Data Protection stack are compound - // of both claims and special authentication properties. To ensure existing tokens - // can be reused, well-known properties are manually mapped to their claims equivalents. - - return principal - .SetAudiences(GetArrayProperty(properties, Properties.Audiences)) - .SetPresenters(GetArrayProperty(properties, Properties.Presenters)) - .SetResources(GetArrayProperty(properties, Properties.Resources)) - .SetScopes(GetArrayProperty(properties, Properties.Scopes)) - - .SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime)) - .SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime)) - .SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId)) - .SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge)) - .SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod)) - .SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued)) - .SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId)) - .SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime)) - .SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime)) - .SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires)) - .SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce)) - .SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri)) - .SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime)) - .SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId)) - .SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime)); - - static (ClaimsPrincipal principal, IReadOnlyDictionary properties) Read(BinaryReader reader) + var (principal, properties) = Read(reader); + + // Tokens serialized using the ASP.NET Core Data Protection stack are compound + // of both claims and special authentication properties. To ensure existing tokens + // can be reused, well-known properties are manually mapped to their claims equivalents. + + return principal + .SetAudiences(GetArrayProperty(properties, Properties.Audiences)) + .SetPresenters(GetArrayProperty(properties, Properties.Presenters)) + .SetResources(GetArrayProperty(properties, Properties.Resources)) + .SetScopes(GetArrayProperty(properties, Properties.Scopes)) + + .SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime)) + .SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime)) + .SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId)) + .SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge)) + .SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod)) + .SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued)) + .SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId)) + .SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime)) + .SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime)) + .SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires)) + .SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce)) + .SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri)) + .SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime)) + .SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId)) + .SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime)); + + static (ClaimsPrincipal principal, IReadOnlyDictionary properties) Read(BinaryReader reader) + { + // Read the version of the format used to serialize the ticket. + var version = reader.ReadInt32(); + if (version != 5) { - // Read the version of the format used to serialize the ticket. - var version = reader.ReadInt32(); - if (version != 5) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0287)); - } - - // Read the authentication scheme associated to the ticket. - _ = reader.ReadString(); - - // Read the number of identities stored in the serialized payload. - var count = reader.ReadInt32(); - - var identities = new ClaimsIdentity[count]; - for (var index = 0; index != count; ++index) - { - identities[index] = ReadIdentity(reader); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0287)); + } - var properties = ReadProperties(reader); + // Read the authentication scheme associated to the ticket. + _ = reader.ReadString(); - return (new ClaimsPrincipal(identities), properties); - } + // Read the number of identities stored in the serialized payload. + var count = reader.ReadInt32(); - static ClaimsIdentity ReadIdentity(BinaryReader reader) + var identities = new ClaimsIdentity[count]; + for (var index = 0; index != count; ++index) { - var identity = new ClaimsIdentity( - authenticationType: reader.ReadString(), - nameType: ReadWithDefault(reader, ClaimsIdentity.DefaultNameClaimType), - roleType: ReadWithDefault(reader, ClaimsIdentity.DefaultRoleClaimType)); + identities[index] = ReadIdentity(reader); + } - // Read the number of claims contained in the serialized identity. - var count = reader.ReadInt32(); + var properties = ReadProperties(reader); - for (int index = 0; index != count; ++index) - { - var claim = ReadClaim(reader, identity); + return (new ClaimsPrincipal(identities), properties); + } - identity.AddClaim(claim); - } + static ClaimsIdentity ReadIdentity(BinaryReader reader) + { + var identity = new ClaimsIdentity( + authenticationType: reader.ReadString(), + nameType: ReadWithDefault(reader, ClaimsIdentity.DefaultNameClaimType), + roleType: ReadWithDefault(reader, ClaimsIdentity.DefaultRoleClaimType)); - // Determine whether the identity has a bootstrap context attached. - if (reader.ReadBoolean()) - { - identity.BootstrapContext = reader.ReadString(); - } + // Read the number of claims contained in the serialized identity. + var count = reader.ReadInt32(); - // Determine whether the identity has an actor identity attached. - if (reader.ReadBoolean()) - { - identity.Actor = ReadIdentity(reader); - } + for (int index = 0; index != count; ++index) + { + var claim = ReadClaim(reader, identity); - return identity; + identity.AddClaim(claim); } - static Claim ReadClaim(BinaryReader reader, ClaimsIdentity identity) + // Determine whether the identity has a bootstrap context attached. + if (reader.ReadBoolean()) { - var type = ReadWithDefault(reader, identity.NameClaimType); - var value = reader.ReadString(); - var valueType = ReadWithDefault(reader, ClaimValueTypes.String); - var issuer = ReadWithDefault(reader, ClaimsIdentity.DefaultIssuer); - var originalIssuer = ReadWithDefault(reader, issuer); + identity.BootstrapContext = reader.ReadString(); + } - var claim = new Claim(type, value, valueType, issuer, originalIssuer, identity); + // Determine whether the identity has an actor identity attached. + if (reader.ReadBoolean()) + { + identity.Actor = ReadIdentity(reader); + } - // Read the number of properties stored in the claim. - var count = reader.ReadInt32(); + return identity; + } - for (var index = 0; index != count; ++index) - { - var key = reader.ReadString(); - var propertyValue = reader.ReadString(); + static Claim ReadClaim(BinaryReader reader, ClaimsIdentity identity) + { + var type = ReadWithDefault(reader, identity.NameClaimType); + var value = reader.ReadString(); + var valueType = ReadWithDefault(reader, ClaimValueTypes.String); + var issuer = ReadWithDefault(reader, ClaimsIdentity.DefaultIssuer); + var originalIssuer = ReadWithDefault(reader, issuer); - claim.Properties.Add(key, propertyValue); - } + var claim = new Claim(type, value, valueType, issuer, originalIssuer, identity); - return claim; - } + // Read the number of properties stored in the claim. + var count = reader.ReadInt32(); - static IReadOnlyDictionary ReadProperties(BinaryReader reader) + for (var index = 0; index != count; ++index) { - // Read the version of the format used to serialize the properties. - var version = reader.ReadInt32(); - if (version != 1) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0287)); - } + var key = reader.ReadString(); + var propertyValue = reader.ReadString(); - var count = reader.ReadInt32(); - var properties = new Dictionary(count, StringComparer.Ordinal); - for (var index = 0; index != count; ++index) - { - properties.Add(reader.ReadString(), reader.ReadString()); - } + claim.Properties.Add(key, propertyValue); + } + + return claim; + } - return properties; + static IReadOnlyDictionary ReadProperties(BinaryReader reader) + { + // Read the version of the format used to serialize the properties. + var version = reader.ReadInt32(); + if (version != 1) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0287)); } - static string ReadWithDefault(BinaryReader reader, string defaultValue) + var count = reader.ReadInt32(); + var properties = new Dictionary(count, StringComparer.Ordinal); + for (var index = 0; index != count; ++index) { - var value = reader.ReadString(); + properties.Add(reader.ReadString(), reader.ReadString()); + } - if (string.Equals(value, "\0", StringComparison.Ordinal)) - { - return defaultValue; - } + return properties; + } - return value; + static string ReadWithDefault(BinaryReader reader, string defaultValue) + { + var value = reader.ReadString(); + + if (string.Equals(value, "\0", StringComparison.Ordinal)) + { + return defaultValue; } - static string? GetProperty(IReadOnlyDictionary properties, string name) - => properties.TryGetValue(name, out var value) ? value : null; + return value; + } - static ImmutableArray GetArrayProperty(IReadOnlyDictionary properties, string name) + static string? GetProperty(IReadOnlyDictionary properties, string name) + => properties.TryGetValue(name, out var value) ? value : null; + + static ImmutableArray GetArrayProperty(IReadOnlyDictionary properties, string name) + { + if (properties.TryGetValue(name, out var value)) { - if (properties.TryGetValue(name, out var value)) - { - using var document = JsonDocument.Parse(value); - var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); + using var document = JsonDocument.Parse(value); + var builder = ImmutableArray.CreateBuilder(document.RootElement.GetArrayLength()); - foreach (var element in document.RootElement.EnumerateArray()) + foreach (var element in document.RootElement.EnumerateArray()) + { + var item = element.GetString(); + if (string.IsNullOrEmpty(item)) { - var item = element.GetString(); - if (string.IsNullOrEmpty(item)) - { - continue; - } - - builder.Add(item); + continue; } - return builder.ToImmutable(); + builder.Add(item); } - return ImmutableArray.Create(); + return builder.ToImmutable(); } + + return ImmutableArray.Create(); } } } diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs index 778450a8..47960667 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs @@ -22,113 +22,112 @@ using static OpenIddict.Validation.OpenIddictValidationHandlers.Protection; using Schemes = OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Purposes.Schemes; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.DataProtection +namespace OpenIddict.Validation.DataProtection; + +public static partial class OpenIddictValidationDataProtectionHandlers { - public static partial class OpenIddictValidationDataProtectionHandlers + public static class Protection { - public static class Protection + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Token validation: + */ + ValidateDataProtectionToken.Descriptor); + + /// + /// Contains the logic responsible of validating tokens generated using Data Protection. + /// + public class ValidateDataProtectionToken : IOpenIddictValidationHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Token validation: - */ - ValidateDataProtectionToken.Descriptor); + private readonly IOptionsMonitor _options; + + public ValidateDataProtectionToken(IOptionsMonitor options) + => _options = options; /// - /// Contains the logic responsible of validating tokens generated using Data Protection. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateDataProtectionToken : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - private readonly IOptionsMonitor _options; - - public ValidateDataProtectionToken(IOptionsMonitor options) - => _options = options; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) + // If a principal was already attached, don't overwrite it. + if (context.Principal is not null) { - // If a principal was already attached, don't overwrite it. - if (context.Principal is not null) - { - return default; - } + return default; + } - // Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds - // to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads. - if (!context.Token.StartsWith("CfDJ8", StringComparison.Ordinal)) - { - return default; - } + // Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds + // to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads. + if (!context.Token.StartsWith("CfDJ8", StringComparison.Ordinal)) + { + return default; + } - // Note: unlike the equivalent handler in the server stack, the logic used here is - // simpler as only access tokens are currently supported by the validation stack. - var principal = context.ValidTokenTypes.Count switch - { - // If no valid token type was set, all supported token types are allowed. - 0 => ValidateToken(TokenTypeHints.AccessToken), + // Note: unlike the equivalent handler in the server stack, the logic used here is + // simpler as only access tokens are currently supported by the validation stack. + var principal = context.ValidTokenTypes.Count switch + { + // If no valid token type was set, all supported token types are allowed. + 0 => ValidateToken(TokenTypeHints.AccessToken), - _ when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) - => ValidateToken(TokenTypeHints.AccessToken), + _ when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) + => ValidateToken(TokenTypeHints.AccessToken), - _ => null // The token type is not supported by the Data Protection integration (e.g identity tokens). - }; + _ => null // The token type is not supported by the Data Protection integration (e.g identity tokens). + }; - if (principal is null) - { - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2004), - uri: SR.FormatID8000(SR.ID2004)); + if (principal is null) + { + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2004), + uri: SR.FormatID8000(SR.ID2004)); - return default; - } + return default; + } - context.Principal = principal; + context.Principal = principal; - context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims); + context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims); - return default; + return default; + + ClaimsPrincipal? ValidateToken(string type) + { + // Create a Data Protection protector using the provider registered in the options. + var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch + { + // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. + TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId) + => new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, + TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, + + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) + }); - ClaimsPrincipal? ValidateToken(string type) + try { - // Create a Data Protection protector using the provider registered in the options. - var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch - { - // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. - TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId) - => new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, - TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, - - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) - }); - - try - { - using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(context.Token))); - using var reader = new BinaryReader(buffer); - - // Note: since the data format relies on a data protector using different "purposes" strings - // per token type, the token processed at this stage is guaranteed to be of the expected type. - return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type); - } - - catch (Exception exception) - { - context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), context.Token); - - return null; - } + using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(context.Token))); + using var reader = new BinaryReader(buffer); + + // Note: since the data format relies on a data protector using different "purposes" strings + // per token type, the token processed at this stage is guaranteed to be of the expected type. + return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type); + } + + catch (Exception exception) + { + context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), context.Token); + + return null; } } } diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs index e27e278a..76dcd326 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs @@ -7,12 +7,11 @@ using System.Collections.Immutable; using System.ComponentModel; -namespace OpenIddict.Validation.DataProtection +namespace OpenIddict.Validation.DataProtection; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class OpenIddictValidationDataProtectionHandlers { - [EditorBrowsable(EditorBrowsableState.Never)] - public static partial class OpenIddictValidationDataProtectionHandlers - { - public static ImmutableArray DefaultHandlers { get; } - = ImmutableArray.CreateRange(Protection.DefaultHandlers); - } + public static ImmutableArray DefaultHandlers { get; } + = ImmutableArray.CreateRange(Protection.DefaultHandlers); } diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionOptions.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionOptions.cs index 71ff5c1a..689bdb6e 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionOptions.cs +++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionOptions.cs @@ -6,26 +6,25 @@ using Microsoft.AspNetCore.DataProtection; -namespace OpenIddict.Validation.DataProtection +namespace OpenIddict.Validation.DataProtection; + +/// +/// Provides various settings needed to configure the OpenIddict +/// ASP.NET Core Data Protection validation integration. +/// +public class OpenIddictValidationDataProtectionOptions { /// - /// Provides various settings needed to configure the OpenIddict - /// ASP.NET Core Data Protection validation integration. + /// Gets or sets the data protection provider used to create the default + /// data protectors used by the OpenIddict Data Protection validation services. + /// When this property is set to null, the data protection provider + /// is directly retrieved from the dependency injection container. /// - public class OpenIddictValidationDataProtectionOptions - { - /// - /// Gets or sets the data protection provider used to create the default - /// data protectors used by the OpenIddict Data Protection validation services. - /// When this property is set to null, the data protection provider - /// is directly retrieved from the dependency injection container. - /// - public IDataProtectionProvider DataProtectionProvider { get; set; } = default!; + public IDataProtectionProvider DataProtectionProvider { get; set; } = default!; - /// - /// Gets or sets the formatter used to read Data Protection tokens. - /// - public IOpenIddictValidationDataProtectionFormatter Formatter { get; set; } - = new OpenIddictValidationDataProtectionFormatter(); - } + /// + /// Gets or sets the formatter used to read Data Protection tokens. + /// + public IOpenIddictValidationDataProtectionFormatter Formatter { get; set; } + = new OpenIddictValidationDataProtectionFormatter(); } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs index 60632950..68ef7de2 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinBuilder.cs @@ -10,85 +10,84 @@ using Microsoft.Owin.Security; using OpenIddict.Validation.Owin; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure +/// the OpenIddict validation OWIN/Katana integration. +/// +public class OpenIddictValidationOwinBuilder { /// - /// Exposes the necessary methods required to configure - /// the OpenIddict validation OWIN/Katana integration. + /// Initializes a new instance of . /// - public class OpenIddictValidationOwinBuilder - { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictValidationOwinBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); + /// The services collection. + public OpenIddictValidationOwinBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } - /// - /// Amends the default OpenIddict validation OWIN/Katana configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictValidationOwinBuilder Configure(Action configuration) + /// + /// Amends the default OpenIddict validation OWIN/Katana configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictValidationOwinBuilder Configure(Action configuration) + { + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Configures the OpenIddict validation OWIN integration to use active authentication. - /// When using active authentication, the principal resolved from the access token is - /// attached to the request context and 401/403 responses are automatically handled without - /// requiring an explicit call to . - /// - /// - /// Using active authentication is strongly discouraged in applications using a cookie - /// authentication middleware configured to use active authentication, as both middleware - /// will be invoked when handling 401 responses, which will result in invalid responses. - /// - /// The . - public OpenIddictValidationOwinBuilder UseActiveAuthentication() - => Configure(options => options.AuthenticationMode = AuthenticationMode.Active); + /// + /// Configures the OpenIddict validation OWIN integration to use active authentication. + /// When using active authentication, the principal resolved from the access token is + /// attached to the request context and 401/403 responses are automatically handled without + /// requiring an explicit call to . + /// + /// + /// Using active authentication is strongly discouraged in applications using a cookie + /// authentication middleware configured to use active authentication, as both middleware + /// will be invoked when handling 401 responses, which will result in invalid responses. + /// + /// The . + public OpenIddictValidationOwinBuilder UseActiveAuthentication() + => Configure(options => options.AuthenticationMode = AuthenticationMode.Active); - /// - /// Sets the realm returned to the caller as part of the WWW-Authenticate header. - /// - /// The issuer address. - /// The . - public OpenIddictValidationOwinBuilder SetRealm(string realm) + /// + /// Sets the realm returned to the caller as part of the WWW-Authenticate header. + /// + /// The issuer address. + /// The . + public OpenIddictValidationOwinBuilder SetRealm(string realm) + { + if (string.IsNullOrEmpty(realm)) { - if (string.IsNullOrEmpty(realm)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0107), nameof(realm)); - } - - return Configure(options => options.Realm = realm); + throw new ArgumentException(SR.GetResourceString(SR.ID0107), nameof(realm)); } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + return Configure(options => options.Realm = realm); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); - } + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConfiguration.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConfiguration.cs index c6035e3b..77e3068b 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConfiguration.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConfiguration.cs @@ -7,22 +7,21 @@ using System; using Microsoft.Extensions.Options; -namespace OpenIddict.Validation.Owin +namespace OpenIddict.Validation.Owin; + +/// +/// Contains the methods required to ensure that the OpenIddict validation configuration is valid. +/// +public class OpenIddictValidationOwinConfiguration : IConfigureOptions { - /// - /// Contains the methods required to ensure that the OpenIddict validation configuration is valid. - /// - public class OpenIddictValidationOwinConfiguration : IConfigureOptions + public void Configure(OpenIddictValidationOptions options) { - public void Configure(OpenIddictValidationOptions options) + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - // Register the built-in event handlers used by the OpenIddict OWIN validation components. - options.Handlers.AddRange(OpenIddictValidationOwinHandlers.DefaultHandlers); + throw new ArgumentNullException(nameof(options)); } + + // Register the built-in event handlers used by the OpenIddict OWIN validation components. + options.Handlers.AddRange(OpenIddictValidationOwinHandlers.DefaultHandlers); } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs index d8b418b0..27edde10 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs @@ -4,37 +4,36 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Validation.Owin +namespace OpenIddict.Validation.Owin; + +/// +/// Exposes common constants used by the OpenIddict OWIN host. +/// +public static class OpenIddictValidationOwinConstants { - /// - /// Exposes common constants used by the OpenIddict OWIN host. - /// - public static class OpenIddictValidationOwinConstants + public static class Cache { - public static class Cache - { - public const string AuthorizationRequest = "openiddict-authorization-request:"; - public const string LogoutRequest = "openiddict-logout-request:"; - } + public const string AuthorizationRequest = "openiddict-authorization-request:"; + public const string LogoutRequest = "openiddict-logout-request:"; + } - public static class Headers - { - public const string Authorization = "Authorization"; - public const string CacheControl = "Cache-Control"; - public const string ContentType = "Content-Type"; - public const string Expires = "Expires"; - public const string Host = "Host"; - public const string Pragma = "Pragma"; - public const string WwwAuthenticate = "WWW-Authenticate"; - } + public static class Headers + { + public const string Authorization = "Authorization"; + public const string CacheControl = "Cache-Control"; + public const string ContentType = "Content-Type"; + public const string Expires = "Expires"; + public const string Host = "Host"; + public const string Pragma = "Pragma"; + public const string WwwAuthenticate = "WWW-Authenticate"; + } - public static class Properties - { - public const string AccessTokenPrincipal = ".access_token_principal"; - public const string Error = ".error"; - public const string ErrorDescription = ".error_description"; - public const string ErrorUri = ".error_uri"; - public const string Scope = ".scope"; - } + public static class Properties + { + public const string AccessTokenPrincipal = ".access_token_principal"; + public const string Error = ".error"; + public const string ErrorDescription = ".error_description"; + public const string ErrorUri = ".error_uri"; + public const string Scope = ".scope"; } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinDefaults.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinDefaults.cs index 78041388..90b1ab03 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinDefaults.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinDefaults.cs @@ -6,16 +6,15 @@ using Microsoft.Owin.Security; -namespace OpenIddict.Validation.Owin +namespace OpenIddict.Validation.Owin; + +/// +/// Exposes the default values used by the OpenIddict validation handler. +/// +public static class OpenIddictValidationOwinDefaults { /// - /// Exposes the default values used by the OpenIddict validation handler. + /// Default value for . /// - public static class OpenIddictValidationOwinDefaults - { - /// - /// Default value for . - /// - public const string AuthenticationType = "OpenIddict.Validation.Owin"; - } + public const string AuthenticationType = "OpenIddict.Validation.Owin"; } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs index 0c6246fe..e899cd6d 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinExtensions.cs @@ -13,71 +13,70 @@ using OpenIddict.Validation.Owin; using static OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlerFilters; using static OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict validation services. +/// +public static class OpenIddictValidationOwinExtensions { /// - /// Exposes extensions allowing to register the OpenIddict validation services. + /// Registers the OpenIddict validation services for OWIN in the DI container. /// - public static class OpenIddictValidationOwinExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationOwinBuilder UseOwin(this OpenIddictValidationBuilder builder) { - /// - /// Registers the OpenIddict validation services for OWIN in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationOwinBuilder UseOwin(this OpenIddictValidationBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - // Note: unlike regular OWIN middleware, the OpenIddict validation middleware is registered - // as a scoped service in the DI container. This allows containers that support middleware - // resolution (like Autofac) to use it without requiring additional configuration. - builder.Services.TryAddScoped(); + // Note: unlike regular OWIN middleware, the OpenIddict validation middleware is registered + // as a scoped service in the DI container. This allows containers that support middleware + // resolution (like Autofac) to use it without requiring additional configuration. + builder.Services.TryAddScoped(); - // Register the built-in event handlers used by the OpenIddict OWIN validation components. - // Note: the order used here is not important, as the actual order is set in the options. - builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); + // Register the built-in event handlers used by the OpenIddict OWIN validation components. + // Note: the order used here is not important, as the actual order is set in the options. + builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); - // Register the built-in filters used by the default OpenIddict OWIN validation event handlers. - builder.Services.TryAddSingleton(); + // Register the built-in filters used by the default OpenIddict OWIN validation event handlers. + builder.Services.TryAddSingleton(); - // Register the option initializers used by the OpenIddict OWIN validation integration services. - // Note: TryAddEnumerable() is used here to ensure the initializers are only registered once. - builder.Services.TryAddEnumerable(new[] - { - ServiceDescriptor.Singleton, OpenIddictValidationOwinConfiguration>() - }); + // Register the option initializers used by the OpenIddict OWIN validation integration services. + // Note: TryAddEnumerable() is used here to ensure the initializers are only registered once. + builder.Services.TryAddEnumerable(new[] + { + ServiceDescriptor.Singleton, OpenIddictValidationOwinConfiguration>() + }); - return new OpenIddictValidationOwinBuilder(builder.Services); - } + return new OpenIddictValidationOwinBuilder(builder.Services); + } - /// - /// Registers the OpenIddict validation services for OWIN in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the validation services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationBuilder UseOwin( - this OpenIddictValidationBuilder builder, Action configuration) + /// + /// Registers the OpenIddict validation services for OWIN in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the validation services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationBuilder UseOwin( + this OpenIddictValidationBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseOwin()); + configuration(builder.UseOwin()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs index 59afe8ff..c2388274 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs @@ -17,239 +17,238 @@ using static OpenIddict.Validation.OpenIddictValidationEvents; using Properties = OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.Owin +namespace OpenIddict.Validation.Owin; + +/// +/// Provides the entry point necessary to register the OpenIddict validation in an OWIN pipeline. +/// +public class OpenIddictValidationOwinHandler : AuthenticationHandler { + private readonly IOpenIddictValidationDispatcher _dispatcher; + private readonly IOpenIddictValidationFactory _factory; + /// - /// Provides the entry point necessary to register the OpenIddict validation in an OWIN pipeline. + /// Creates a new instance of the class. /// - public class OpenIddictValidationOwinHandler : AuthenticationHandler + /// The OpenIddict validation provider used by this instance. + /// The OpenIddict validation factory used by this instance. + public OpenIddictValidationOwinHandler( + IOpenIddictValidationDispatcher dispatcher, + IOpenIddictValidationFactory factory) + { + _dispatcher = dispatcher; + _factory = factory; + } + + /// + protected override async Task InitializeCoreAsync() { - private readonly IOpenIddictValidationDispatcher _dispatcher; - private readonly IOpenIddictValidationFactory _factory; - - /// - /// Creates a new instance of the class. - /// - /// The OpenIddict validation provider used by this instance. - /// The OpenIddict validation factory used by this instance. - public OpenIddictValidationOwinHandler( - IOpenIddictValidationDispatcher dispatcher, - IOpenIddictValidationFactory factory) + // Note: the transaction may be already attached when replaying an OWIN request + // (e.g when using a status code pages middleware re-invoking the OWIN pipeline). + var transaction = Context.Get(typeof(OpenIddictValidationTransaction).FullName); + if (transaction is null) { - _dispatcher = dispatcher; - _factory = factory; + // Create a new transaction and attach the OWIN request to make it available to the OWIN handlers. + transaction = await _factory.CreateTransactionAsync(); + transaction.Properties[typeof(IOwinRequest).FullName!] = new WeakReference(Request); + + // Attach the OpenIddict validation transaction to the OWIN shared dictionary + // so that it can retrieved while performing sign-in/sign-out operations. + Context.Set(typeof(OpenIddictValidationTransaction).FullName, transaction); } - /// - protected override async Task InitializeCoreAsync() - { - // Note: the transaction may be already attached when replaying an OWIN request - // (e.g when using a status code pages middleware re-invoking the OWIN pipeline). - var transaction = Context.Get(typeof(OpenIddictValidationTransaction).FullName); - if (transaction is null) - { - // Create a new transaction and attach the OWIN request to make it available to the OWIN handlers. - transaction = await _factory.CreateTransactionAsync(); - transaction.Properties[typeof(IOwinRequest).FullName!] = new WeakReference(Request); + var context = new ProcessRequestContext(transaction); + await _dispatcher.DispatchAsync(context); - // Attach the OpenIddict validation transaction to the OWIN shared dictionary - // so that it can retrieved while performing sign-in/sign-out operations. - Context.Set(typeof(OpenIddictValidationTransaction).FullName, transaction); - } + // Store the context in the transaction so that it can be retrieved from InvokeAsync(). + transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context); + } - var context = new ProcessRequestContext(transaction); - await _dispatcher.DispatchAsync(context); + /// + public override async Task InvokeAsync() + { + // Note: due to internal differences between ASP.NET Core and Katana, the request MUST start being processed + // in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when + // active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case. - // Store the context in the transaction so that it can be retrieved from InvokeAsync(). - transaction.SetProperty(typeof(ProcessRequestContext).FullName!, context); + var transaction = Context.Get(typeof(OpenIddictValidationTransaction).FullName) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); + + var context = transaction.GetProperty(typeof(ProcessRequestContext).FullName!) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); + + if (context.IsRequestHandled) + { + return true; } - /// - public override async Task InvokeAsync() + else if (context.IsRequestSkipped) { - // Note: due to internal differences between ASP.NET Core and Katana, the request MUST start being processed - // in InitializeCoreAsync() to ensure the request context is available from AuthenticateCoreAsync() when - // active authentication is used, as AuthenticateCoreAsync() is always called before InvokeAsync() in this case. + return false; + } - var transaction = Context.Get(typeof(OpenIddictValidationTransaction).FullName) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); + else if (context.IsRejected) + { + var notification = new ProcessErrorContext(transaction) + { + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, + Response = new OpenIddictResponse() + }; - var context = transaction.GetProperty(typeof(ProcessRequestContext).FullName!) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); + await _dispatcher.DispatchAsync(notification); - if (context.IsRequestHandled) + if (notification.IsRequestHandled) { return true; } - else if (context.IsRequestSkipped) + else if (notification.IsRequestSkipped) { return false; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); + } - await _dispatcher.DispatchAsync(notification); + return false; + } - if (notification.IsRequestHandled) - { - return true; - } + /// + protected override async Task AuthenticateCoreAsync() + { + var transaction = Context.Get(typeof(OpenIddictValidationTransaction).FullName) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); + + // Note: in many cases, the authentication token was already validated by the time this action is called + // (generally later in the pipeline, when using the pass-through mode). To avoid having to re-validate it, + // the authentication context is resolved from the transaction. If it's not available, a new one is created. + var context = transaction.GetProperty(typeof(ProcessAuthenticationContext).FullName!); + if (context is null) + { + context = new ProcessAuthenticationContext(transaction); + await _dispatcher.DispatchAsync(context); - else if (notification.IsRequestSkipped) - { - return false; - } + // Store the context object in the transaction so it can be later retrieved by handlers + // that want to access the authentication result without triggering a new authentication flow. + transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, context); + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return null; + } + + else if (context.IsRejected) + { + // Note: the missing_token error is special-cased to indicate to Katana + // that no authentication result could be produced due to the lack of token. + // This also helps reducing the logging noise when no token is specified. + if (string.Equals(context.Error, Errors.MissingToken, StringComparison.Ordinal)) + { + return null; } - return false; + var properties = new AuthenticationProperties(new Dictionary + { + [Properties.Error] = context.Error, + [Properties.ErrorDescription] = context.ErrorDescription, + [Properties.ErrorUri] = context.ErrorUri + }); + + return new AuthenticationTicket(null, properties); } - /// - protected override async Task AuthenticateCoreAsync() + else { - var transaction = Context.Get(typeof(OpenIddictValidationTransaction).FullName) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); - - // Note: in many cases, the authentication token was already validated by the time this action is called - // (generally later in the pipeline, when using the pass-through mode). To avoid having to re-validate it, - // the authentication context is resolved from the transaction. If it's not available, a new one is created. - var context = transaction.GetProperty(typeof(ProcessAuthenticationContext).FullName!); - if (context is null) + // A single main claims-based principal instance can be attached to an authentication ticket. + var principal = context.EndpointType switch { - context = new ProcessAuthenticationContext(transaction); - await _dispatcher.DispatchAsync(context); + OpenIddictValidationEndpointType.Unknown => context.AccessTokenPrincipal, - // Store the context object in the transaction so it can be later retrieved by handlers - // that want to access the authentication result without triggering a new authentication flow. - transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, context); - } + _ => null + }; - if (context.IsRequestHandled || context.IsRequestSkipped) + if (principal is null) { return null; } - else if (context.IsRejected) + var properties = new AuthenticationProperties { - // Note: the missing_token error is special-cased to indicate to Katana - // that no authentication result could be produced due to the lack of token. - // This also helps reducing the logging noise when no token is specified. - if (string.Equals(context.Error, Errors.MissingToken, StringComparison.Ordinal)) - { - return null; - } + ExpiresUtc = principal.GetExpirationDate(), + IssuedUtc = principal.GetCreationDate() + }; - var properties = new AuthenticationProperties(new Dictionary - { - [Properties.Error] = context.Error, - [Properties.ErrorDescription] = context.ErrorDescription, - [Properties.ErrorUri] = context.ErrorUri - }); - - return new AuthenticationTicket(null, properties); - } + // Attach the tokens to allow any OWIN/Katana component (e.g a controller) + // to retrieve them (e.g to make an API request to another application). - else + if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken)) { - // A single main claims-based principal instance can be attached to an authentication ticket. - var principal = context.EndpointType switch - { - OpenIddictValidationEndpointType.Unknown => context.AccessTokenPrincipal, + properties.Dictionary[TokenTypeHints.AccessToken] = context.AccessToken; + } - _ => null - }; + return new AuthenticationTicket((ClaimsIdentity) principal.Identity, properties); + } + } - if (principal is null) - { - return null; - } + /// + protected override async Task TeardownCoreAsync() + { + // Note: OWIN authentication handlers cannot reliabily write to the response stream + // from ApplyResponseGrantAsync or ApplyResponseChallengeAsync because these methods + // are susceptible to be invoked from AuthenticationHandler.OnSendingHeaderCallback, + // where calling Write or WriteAsync on the response stream may result in a deadlock + // on hosts using streamed responses. To work around this limitation, this handler + // doesn't implement ApplyResponseGrantAsync but TeardownCoreAsync, which is never called + // by AuthenticationHandler.OnSendingHeaderCallback. In theory, this would prevent + // OpenIddictValidationOwinMiddleware from both applying the response grant and allowing + // the next middleware in the pipeline to alter the response stream but in practice, + // OpenIddictValidationOwinMiddleware is assumed to be the only middleware allowed to write + // to the response stream when a response grant (sign-in/out or challenge) was applied. + + // Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code + // corresponds to a challenge response, as LookupChallenge() will always return a non-null + // value when active authentication is used, even if no challenge was actually triggered. + var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); + if (challenge is not null && Response.StatusCode is 401 or 403) + { + var transaction = Context.Get(typeof(OpenIddictValidationTransaction).FullName) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); - var properties = new AuthenticationProperties - { - ExpiresUtc = principal.GetExpirationDate(), - IssuedUtc = principal.GetCreationDate() - }; + transaction.Properties[typeof(AuthenticationProperties).FullName!] = challenge.Properties ?? new AuthenticationProperties(); - // Attach the tokens to allow any OWIN/Katana component (e.g a controller) - // to retrieve them (e.g to make an API request to another application). + var context = new ProcessChallengeContext(transaction) + { + Response = new OpenIddictResponse() + }; - if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken)) - { - properties.Dictionary[TokenTypeHints.AccessToken] = context.AccessToken; - } + await _dispatcher.DispatchAsync(context); - return new AuthenticationTicket((ClaimsIdentity) principal.Identity, properties); + if (context.IsRequestHandled || context.IsRequestSkipped) + { + return; } - } - /// - protected override async Task TeardownCoreAsync() - { - // Note: OWIN authentication handlers cannot reliabily write to the response stream - // from ApplyResponseGrantAsync or ApplyResponseChallengeAsync because these methods - // are susceptible to be invoked from AuthenticationHandler.OnSendingHeaderCallback, - // where calling Write or WriteAsync on the response stream may result in a deadlock - // on hosts using streamed responses. To work around this limitation, this handler - // doesn't implement ApplyResponseGrantAsync but TeardownCoreAsync, which is never called - // by AuthenticationHandler.OnSendingHeaderCallback. In theory, this would prevent - // OpenIddictValidationOwinMiddleware from both applying the response grant and allowing - // the next middleware in the pipeline to alter the response stream but in practice, - // OpenIddictValidationOwinMiddleware is assumed to be the only middleware allowed to write - // to the response stream when a response grant (sign-in/out or challenge) was applied. - - // Note: unlike the ASP.NET Core host, the OWIN host MUST check whether the status code - // corresponds to a challenge response, as LookupChallenge() will always return a non-null - // value when active authentication is used, even if no challenge was actually triggered. - var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); - if (challenge is not null && Response.StatusCode is 401 or 403) + else if (context.IsRejected) { - var transaction = Context.Get(typeof(OpenIddictValidationTransaction).FullName) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0166)); - - transaction.Properties[typeof(AuthenticationProperties).FullName!] = challenge.Properties ?? new AuthenticationProperties(); - - var context = new ProcessChallengeContext(transaction) + var notification = new ProcessErrorContext(transaction) { + Error = context.Error ?? Errors.InvalidRequest, + ErrorDescription = context.ErrorDescription, + ErrorUri = context.ErrorUri, Response = new OpenIddictResponse() }; - await _dispatcher.DispatchAsync(context); + await _dispatcher.DispatchAsync(notification); - if (context.IsRequestHandled || context.IsRequestSkipped) + if (notification.IsRequestHandled || context.IsRequestSkipped) { return; } - else if (context.IsRejected) - { - var notification = new ProcessErrorContext(transaction) - { - Error = context.Error ?? Errors.InvalidRequest, - ErrorDescription = context.ErrorDescription, - ErrorUri = context.ErrorUri, - Response = new OpenIddictResponse() - }; - - await _dispatcher.DispatchAsync(notification); - - if (notification.IsRequestHandled || context.IsRequestSkipped) - { - return; - } - - throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0111)); } } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlerFilters.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlerFilters.cs index 0c569299..3e040ac6 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlerFilters.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlerFilters.cs @@ -9,27 +9,26 @@ using System.Threading.Tasks; using Owin; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace OpenIddict.Validation.Owin +namespace OpenIddict.Validation.Owin; + +/// +/// Contains a collection of event handler filters commonly used by the OWIN handlers. +/// +public static class OpenIddictValidationOwinHandlerFilters { /// - /// Contains a collection of event handler filters commonly used by the OWIN handlers. + /// Represents a filter that excludes the associated handlers if no OWIN request can be found. /// - public static class OpenIddictValidationOwinHandlerFilters + public class RequireOwinRequest : IOpenIddictValidationHandlerFilter { - /// - /// Represents a filter that excludes the associated handlers if no OWIN request can be found. - /// - public class RequireOwinRequest : IOpenIddictValidationHandlerFilter + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Transaction.GetOwinRequest() is not null); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Transaction.GetOwinRequest() is not null); } } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs index f4ace9e1..5565dec4 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs @@ -27,645 +27,644 @@ using static OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlerFilters; using Properties = OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants.Properties; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.Owin +namespace OpenIddict.Validation.Owin; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class OpenIddictValidationOwinHandlers { - [EditorBrowsable(EditorBrowsableState.Never)] - public static partial class OpenIddictValidationOwinHandlers + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Request top-level processing: + */ + InferIssuerFromHost.Descriptor, + + /* + * Authentication processing: + */ + ExtractAccessTokenFromAuthorizationHeader.Descriptor, + ExtractAccessTokenFromBodyForm.Descriptor, + ExtractAccessTokenFromQueryString.Descriptor, + + /* + * Challenge processing: + */ + AttachHostChallengeError.Descriptor, + + /* + * Response processing: + */ + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessChallengeErrorResponse.Descriptor, + ProcessJsonResponse.Descriptor, + + AttachHttpResponseCode.Descriptor, + AttachCacheControlHeader.Descriptor, + AttachWwwAuthenticateHeader.Descriptor, + ProcessChallengeErrorResponse.Descriptor, + ProcessJsonResponse.Descriptor); + + /// + /// Contains the logic responsible of infering the default issuer from the HTTP request host and validating it. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class InferIssuerFromHost : IOpenIddictValidationHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Request top-level processing: - */ - InferIssuerFromHost.Descriptor, - - /* - * Authentication processing: - */ - ExtractAccessTokenFromAuthorizationHeader.Descriptor, - ExtractAccessTokenFromBodyForm.Descriptor, - ExtractAccessTokenFromQueryString.Descriptor, - - /* - * Challenge processing: - */ - AttachHostChallengeError.Descriptor, - - /* - * Response processing: - */ - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessChallengeErrorResponse.Descriptor, - ProcessJsonResponse.Descriptor, - - AttachHttpResponseCode.Descriptor, - AttachCacheControlHeader.Descriptor, - AttachWwwAuthenticateHeader.Descriptor, - ProcessChallengeErrorResponse.Descriptor, - ProcessJsonResponse.Descriptor); - /// - /// Contains the logic responsible of infering the default issuer from the HTTP request host and validating it. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class InferIssuerFromHost : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessRequestContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } - - // Only use the current host as the issuer if the - // issuer was not explicitly set in the options. - if (context.Issuer is not null) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (string.IsNullOrEmpty(request.Host.Value)) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2081(Headers.Host), - uri: SR.FormatID8000(SR.ID2081)); + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - return default; - } + // Only use the current host as the issuer if the + // issuer was not explicitly set in the options. + if (context.Issuer is not null) + { + return default; + } - if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase, UriKind.Absolute, out Uri? issuer) || - !issuer.IsWellFormedOriginalString()) - { - context.Reject( - error: Errors.InvalidRequest, - description: SR.FormatID2082(Headers.Host), - uri: SR.FormatID8000(SR.ID2082)); + if (string.IsNullOrEmpty(request.Host.Value)) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2081(Headers.Host), + uri: SR.FormatID8000(SR.ID2081)); - return default; - } + return default; + } - context.Issuer = issuer; + if (!Uri.TryCreate(request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase, UriKind.Absolute, out Uri? issuer) || + !issuer.IsWellFormedOriginalString()) + { + context.Reject( + error: Errors.InvalidRequest, + description: SR.FormatID2082(Headers.Host), + uri: SR.FormatID8000(SR.ID2082)); return default; } + + context.Issuer = issuer; + + return default; } + } + /// + /// Contains the logic responsible of extracting the access token from the standard HTTP Authorization header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ExtractAccessTokenFromAuthorizationHeader : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the access token from the standard HTTP Authorization header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractAccessTokenFromAuthorizationHeader : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // If a token was already resolved, don't overwrite it. - if (!string.IsNullOrEmpty(context.AccessToken)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // If a token was already resolved, don't overwrite it. + if (!string.IsNullOrEmpty(context.AccessToken)) + { + return default; + } - // Resolve the access token from the standard Authorization header. - // See https://tools.ietf.org/html/rfc6750#section-2.1 for more information. - string header = request.Headers[Headers.Authorization]; - if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) - { - context.AccessToken = header.Substring("Bearer ".Length); + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - return default; - } + // Resolve the access token from the standard Authorization header. + // See https://tools.ietf.org/html/rfc6750#section-2.1 for more information. + string header = request.Headers[Headers.Authorization]; + if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { + context.AccessToken = header.Substring("Bearer ".Length); return default; } + + return default; } + } + /// + /// Contains the logic responsible of extracting the access token from the standard access_token form parameter. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ExtractAccessTokenFromBodyForm : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the access token from the standard access_token form parameter. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractAccessTokenFromBodyForm : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ExtractAccessTokenFromAuthorizationHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ExtractAccessTokenFromAuthorizationHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If a token was already resolved, don't overwrite it. - if (!string.IsNullOrEmpty(context.AccessToken)) - { - return; - } + // If a token was already resolved, don't overwrite it. + if (!string.IsNullOrEmpty(context.AccessToken)) + { + return; + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - if (string.IsNullOrEmpty(request.ContentType) || - !request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) - { - return; - } + if (string.IsNullOrEmpty(request.ContentType) || + !request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) + { + return; + } - // Resolve the access token from the standard access_token form parameter. - // See https://tools.ietf.org/html/rfc6750#section-2.2 for more information. - var form = await request.ReadFormAsync(); - string token = form[Parameters.AccessToken]; - if (!string.IsNullOrEmpty(token)) - { - context.AccessToken = token; + // Resolve the access token from the standard access_token form parameter. + // See https://tools.ietf.org/html/rfc6750#section-2.2 for more information. + var form = await request.ReadFormAsync(); + string token = form[Parameters.AccessToken]; + if (!string.IsNullOrEmpty(token)) + { + context.AccessToken = token; - return; - } + return; } } + } + /// + /// Contains the logic responsible of extracting the access token from the standard access_token query parameter. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ExtractAccessTokenFromQueryString : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the access token from the standard access_token query parameter. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractAccessTokenFromQueryString : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ExtractAccessTokenFromBodyForm.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ExtractAccessTokenFromBodyForm.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // If a token was already resolved, don't overwrite it. - if (!string.IsNullOrEmpty(context.AccessToken)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var request = context.Transaction.GetOwinRequest(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // If a token was already resolved, don't overwrite it. + if (!string.IsNullOrEmpty(context.AccessToken)) + { + return default; + } - // Resolve the access token from the standard access_token query parameter. - // See https://tools.ietf.org/html/rfc6750#section-2.3 for more information. - string token = request.Query[Parameters.AccessToken]; - if (!string.IsNullOrEmpty(token)) - { - context.AccessToken = token; + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var request = context.Transaction.GetOwinRequest(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - return default; - } + // Resolve the access token from the standard access_token query parameter. + // See https://tools.ietf.org/html/rfc6750#section-2.3 for more information. + string token = request.Query[Parameters.AccessToken]; + if (!string.IsNullOrEmpty(token)) + { + context.AccessToken = token; return default; } + + return default; } + } + /// + /// Contains the logic responsible of attaching the error details using the OWIN authentication properties. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class AttachHostChallengeError : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of attaching the error details using the OWIN authentication properties. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachHostChallengeError : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 50_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 50_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessChallengeContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); - if (properties is null) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (properties.Dictionary.TryGetValue(Properties.Error, out string? error) && - !string.IsNullOrEmpty(error)) - { - context.Parameters[Parameters.Error] = error; - } + var properties = context.Transaction.GetProperty(typeof(AuthenticationProperties).FullName!); + if (properties is null) + { + return default; + } - if (properties.Dictionary.TryGetValue(Properties.ErrorDescription, out string? description) && - !string.IsNullOrEmpty(description)) - { - context.Parameters[Parameters.ErrorDescription] = description; - } + if (properties.Dictionary.TryGetValue(Properties.Error, out string? error) && + !string.IsNullOrEmpty(error)) + { + context.Parameters[Parameters.Error] = error; + } - if (properties.Dictionary.TryGetValue(Properties.ErrorUri, out string? uri) && - !string.IsNullOrEmpty(uri)) - { - context.Parameters[Parameters.ErrorUri] = uri; - } + if (properties.Dictionary.TryGetValue(Properties.ErrorDescription, out string? description) && + !string.IsNullOrEmpty(description)) + { + context.Parameters[Parameters.ErrorDescription] = description; + } - if (properties.Dictionary.TryGetValue(Properties.Scope, out string? scope) && - !string.IsNullOrEmpty(scope)) - { - context.Parameters[Parameters.Scope] = scope; - } + if (properties.Dictionary.TryGetValue(Properties.ErrorUri, out string? uri) && + !string.IsNullOrEmpty(uri)) + { + context.Parameters[Parameters.ErrorUri] = uri; + } - return default; + if (properties.Dictionary.TryGetValue(Properties.Scope, out string? scope) && + !string.IsNullOrEmpty(scope)) + { + context.Parameters[Parameters.Scope] = scope; } + + return default; } + } + /// + /// Contains the logic responsible of attaching an appropriate HTTP status code. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class AttachHttpResponseCode : IOpenIddictValidationHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of attaching an appropriate HTTP status code. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachHttpResponseCode : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - response.StatusCode = context.Transaction.Response.Error switch - { - null => 200, + response.StatusCode = context.Transaction.Response.Error switch + { + null => 200, - Errors.InvalidToken or Errors.MissingToken => 401, + Errors.InvalidToken or Errors.MissingToken => 401, - Errors.InsufficientAccess or Errors.InsufficientScope => 403, + Errors.InsufficientAccess or Errors.InsufficientScope => 403, - _ => 400 - }; + _ => 400 + }; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the appropriate HTTP response cache headers. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class AttachCacheControlHeader : IOpenIddictValidationHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of attaching the appropriate HTTP response cache headers. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachCacheControlHeader : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachHttpResponseCode.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachHttpResponseCode.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - // Prevent the response from being cached. - response.Headers[Headers.CacheControl] = "no-store"; - response.Headers[Headers.Pragma] = "no-cache"; - response.Headers[Headers.Expires] = "Thu, 01 Jan 1970 00:00:00 GMT"; + // Prevent the response from being cached. + response.Headers[Headers.CacheControl] = "no-store"; + response.Headers[Headers.Pragma] = "no-cache"; + response.Headers[Headers.Expires] = "Thu, 01 Jan 1970 00:00:00 GMT"; - return default; - } + return default; } + } + + /// + /// Contains the logic responsible of attaching errors details to the WWW-Authenticate header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class AttachWwwAuthenticateHeader : IOpenIddictValidationHandler where TContext : BaseRequestContext + { + private readonly IOptionsMonitor _options; + + public AttachWwwAuthenticateHeader(IOptionsMonitor options) + => _options = options; /// - /// Contains the logic responsible of attaching errors details to the WWW-Authenticate header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachWwwAuthenticateHeader : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachCacheControlHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - private readonly IOptionsMonitor _options; - - public AttachWwwAuthenticateHeader(IOptionsMonitor options) - => _options = options; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachCacheControlHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - if (string.IsNullOrEmpty(context.Transaction.Response.Error)) - { - return default; - } + if (string.IsNullOrEmpty(context.Transaction.Response.Error)) + { + return default; + } - var scheme = context.Transaction.Response.Error switch - { - Errors.InvalidToken or - Errors.MissingToken or - Errors.InsufficientAccess or - Errors.InsufficientScope => Schemes.Bearer, + var scheme = context.Transaction.Response.Error switch + { + Errors.InvalidToken or + Errors.MissingToken or + Errors.InsufficientAccess or + Errors.InsufficientScope => Schemes.Bearer, - _ => null - }; + _ => null + }; - if (string.IsNullOrEmpty(scheme)) - { - return default; - } + if (string.IsNullOrEmpty(scheme)) + { + return default; + } - var parameters = new Dictionary(StringComparer.Ordinal); + var parameters = new Dictionary(StringComparer.Ordinal); - // If a realm was configured in the options, attach it to the parameters. - if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) - { - parameters[Parameters.Realm] = _options.CurrentValue.Realm; - } + // If a realm was configured in the options, attach it to the parameters. + if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) + { + parameters[Parameters.Realm] = _options.CurrentValue.Realm; + } - foreach (var parameter in context.Transaction.Response.GetParameters()) + foreach (var parameter in context.Transaction.Response.GetParameters()) + { + // Note: the error details are only included if the error was not caused by a missing token, as recommended + // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. + if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && + (string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || + string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || + string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) { - // Note: the error details are only included if the error was not caused by a missing token, as recommended - // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. - if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && - (string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || - string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || - string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) - { - continue; - } - - // Ignore values that can't be represented as unique strings. - var value = (string?) parameter.Value; - if (string.IsNullOrEmpty(value)) - { - continue; - } - - parameters[parameter.Key] = value; + continue; } - var builder = new StringBuilder(scheme); - - foreach (var parameter in parameters) + // Ignore values that can't be represented as unique strings. + var value = (string?) parameter.Value; + if (string.IsNullOrEmpty(value)) { - builder.Append(' '); - builder.Append(parameter.Key); - builder.Append('='); - builder.Append('"'); - builder.Append(parameter.Value.Replace("\"", "\\\"")); - builder.Append('"'); - builder.Append(','); + continue; } - // If the WWW-Authenticate header ends with a comma, remove it. - if (builder[builder.Length - 1] == ',') - { - builder.Remove(builder.Length - 1, 1); - } + parameters[parameter.Key] = value; + } - response.Headers.Append(Headers.WwwAuthenticate, builder.ToString()); + var builder = new StringBuilder(scheme); - return default; + foreach (var parameter in parameters) + { + builder.Append(' '); + builder.Append(parameter.Key); + builder.Append('='); + builder.Append('"'); + builder.Append(parameter.Value.Replace("\"", "\\\"")); + builder.Append('"'); + builder.Append(','); + } + + // If the WWW-Authenticate header ends with a comma, remove it. + if (builder[builder.Length - 1] == ',') + { + builder.Remove(builder.Length - 1, 1); } + + response.Headers.Append(Headers.WwwAuthenticate, builder.ToString()); + + return default; } + } + /// + /// Contains the logic responsible of processing challenge responses that contain a WWW-Authenticate header. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessChallengeErrorResponse : IOpenIddictValidationHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing challenge responses that contain a WWW-Authenticate header. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessChallengeErrorResponse : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachWwwAuthenticateHeader.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachWwwAuthenticateHeader.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } - - // If the response doesn't contain a WWW-Authenticate header, don't return an empty response. - if (!response.Headers.ContainsKey(Headers.WwwAuthenticate)) - { - return default; - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6141), context.Transaction.Response); - context.HandleRequest(); + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } + // If the response doesn't contain a WWW-Authenticate header, don't return an empty response. + if (!response.Headers.ContainsKey(Headers.WwwAuthenticate)) + { return default; } + + context.Logger.LogInformation(SR.GetResourceString(SR.ID6141), context.Transaction.Response); + context.HandleRequest(); + + return default; } + } + /// + /// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON. + /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// + public class ProcessJsonResponse : IOpenIddictValidationHandler where TContext : BaseRequestContext + { /// - /// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON. - /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. + /// Gets the default descriptor definition assigned to this handler. /// - public class ProcessJsonResponse : IOpenIddictValidationHandler where TContext : BaseRequestContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(ProcessChallengeErrorResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(ProcessChallengeErrorResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); + Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); - // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, - // this may indicate that the request was incorrectly processed by another server stack. - var response = context.Transaction.GetOwinRequest()?.Context.Response; - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); - } + // This handler only applies to OWIN requests. If The OWIN request cannot be resolved, + // this may indicate that the request was incorrectly processed by another server stack. + var response = context.Transaction.GetOwinRequest()?.Context.Response; + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); + } - context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response); + context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response); - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = true - }); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = true + }); - context.Transaction.Response.WriteTo(writer); - writer.Flush(); + context.Transaction.Response.WriteTo(writer); + writer.Flush(); - response.ContentLength = stream.Length; - response.ContentType = "application/json;charset=UTF-8"; + response.ContentLength = stream.Length; + response.ContentType = "application/json;charset=UTF-8"; - stream.Seek(offset: 0, loc: SeekOrigin.Begin); - await stream.CopyToAsync(response.Body, 4096, response.Context.Request.CallCancelled); + stream.Seek(offset: 0, loc: SeekOrigin.Begin); + await stream.CopyToAsync(response.Body, 4096, response.Context.Request.CallCancelled); - context.HandleRequest(); - } + context.HandleRequest(); } } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHelpers.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHelpers.cs index c7199128..6f5ee2d6 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHelpers.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHelpers.cs @@ -11,98 +11,97 @@ using OpenIddict.Validation; using OpenIddict.Validation.Owin; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace Owin +namespace Owin; + +/// +/// Exposes companion extensions for the OpenIddict/OWIN integration. +/// +public static class OpenIddictValidationOwinHelpers { /// - /// Exposes companion extensions for the OpenIddict/OWIN integration. + /// Registers the OpenIddict validation OWIN middleware in the application pipeline. + /// Note: when using a dependency injection container supporting per-request + /// middleware resolution (like Autofac), calling this method is NOT recommended. /// - public static class OpenIddictValidationOwinHelpers + /// The application builder used to register middleware instances. + /// The . + public static IAppBuilder UseOpenIddictValidation(this IAppBuilder app) { - /// - /// Registers the OpenIddict validation OWIN middleware in the application pipeline. - /// Note: when using a dependency injection container supporting per-request - /// middleware resolution (like Autofac), calling this method is NOT recommended. - /// - /// The application builder used to register middleware instances. - /// The . - public static IAppBuilder UseOpenIddictValidation(this IAppBuilder app) + if (app is null) { - if (app is null) - { - throw new ArgumentNullException(nameof(app)); - } - - return app.Use(); + throw new ArgumentNullException(nameof(app)); } - /// - /// Retrieves the instance stored in the properties. - /// - /// The transaction instance. - /// The instance or null if it couldn't be found. - public static IOwinRequest? GetOwinRequest(this OpenIddictValidationTransaction transaction) - { - if (transaction is null) - { - throw new ArgumentNullException(nameof(transaction)); - } - - if (!transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)) - { - return null; - } + return app.Use(); + } - if (property is WeakReference reference && reference.TryGetTarget(out IOwinRequest? request)) - { - return request; - } + /// + /// Retrieves the instance stored in the properties. + /// + /// The transaction instance. + /// The instance or null if it couldn't be found. + public static IOwinRequest? GetOwinRequest(this OpenIddictValidationTransaction transaction) + { + if (transaction is null) + { + throw new ArgumentNullException(nameof(transaction)); + } + if (!transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName!, out object? property)) + { return null; } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The . - public static OpenIddictValidationEndpointType GetOpenIddictValidationEndpointType(this IOwinContext context) + if (property is WeakReference reference && reference.TryGetTarget(out IOwinRequest? request)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return context.Get(typeof(OpenIddictValidationTransaction).FullName)?.EndpointType ?? default; + return request; } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The instance or null if it couldn't be found. - public static OpenIddictRequest? GetOpenIddictValidationRequest(this IOwinContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + return null; + } - return context.Get(typeof(OpenIddictValidationTransaction).FullName)?.Request; + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The . + public static OpenIddictValidationEndpointType GetOpenIddictValidationEndpointType(this IOwinContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); } - /// - /// Retrieves the instance stored in . - /// - /// The context instance. - /// The instance or null if it couldn't be found. - public static OpenIddictResponse? GetOpenIddictValidationResponse(this IOwinContext context) + return context.Get(typeof(OpenIddictValidationTransaction).FullName)?.EndpointType ?? default; + } + + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The instance or null if it couldn't be found. + public static OpenIddictRequest? GetOpenIddictValidationRequest(this IOwinContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - return context.Get(typeof(OpenIddictValidationTransaction).FullName)?.Response; + return context.Get(typeof(OpenIddictValidationTransaction).FullName)?.Request; + } + + /// + /// Retrieves the instance stored in . + /// + /// The context instance. + /// The instance or null if it couldn't be found. + public static OpenIddictResponse? GetOpenIddictValidationResponse(this IOwinContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); } + + return context.Get(typeof(OpenIddictValidationTransaction).FullName)?.Response; } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddleware.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddleware.cs index a02384bf..0bc65750 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddleware.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddleware.cs @@ -8,42 +8,41 @@ using Microsoft.Extensions.Options; using Microsoft.Owin; using Microsoft.Owin.Security.Infrastructure; -namespace OpenIddict.Validation.Owin +namespace OpenIddict.Validation.Owin; + +/// +/// Provides the entry point necessary to register the OpenIddict validation handler in an OWIN pipeline. +/// Note: this middleware is intented to be used with dependency injection containers +/// that support middleware resolution, like Autofac. Since it depends on scoped services, +/// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware. +/// +public class OpenIddictValidationOwinMiddleware : AuthenticationMiddleware { + private readonly IOpenIddictValidationDispatcher _dispatcher; + private readonly IOpenIddictValidationFactory _factory; + /// - /// Provides the entry point necessary to register the OpenIddict validation handler in an OWIN pipeline. - /// Note: this middleware is intented to be used with dependency injection containers - /// that support middleware resolution, like Autofac. Since it depends on scoped services, - /// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware. + /// Creates a new instance of the class. /// - public class OpenIddictValidationOwinMiddleware : AuthenticationMiddleware + /// The next middleware in the pipeline, if applicable. + /// The OpenIddict validation OWIN options. + /// The OpenIddict validation dispatcher. + /// The OpenIddict validation factory. + public OpenIddictValidationOwinMiddleware( + OwinMiddleware? next, + IOptionsMonitor options, + IOpenIddictValidationDispatcher dispatcher, + IOpenIddictValidationFactory factory) + : base(next, options.CurrentValue) { - private readonly IOpenIddictValidationDispatcher _dispatcher; - private readonly IOpenIddictValidationFactory _factory; - - /// - /// Creates a new instance of the class. - /// - /// The next middleware in the pipeline, if applicable. - /// The OpenIddict validation OWIN options. - /// The OpenIddict validation dispatcher. - /// The OpenIddict validation factory. - public OpenIddictValidationOwinMiddleware( - OwinMiddleware? next, - IOptionsMonitor options, - IOpenIddictValidationDispatcher dispatcher, - IOpenIddictValidationFactory factory) - : base(next, options.CurrentValue) - { - _dispatcher = dispatcher; - _factory = factory; - } - - /// - /// Creates and returns a new instance. - /// - /// A new instance of the class. - protected override AuthenticationHandler CreateHandler() - => new OpenIddictValidationOwinHandler(_dispatcher, _factory); + _dispatcher = dispatcher; + _factory = factory; } + + /// + /// Creates and returns a new instance. + /// + /// A new instance of the class. + protected override AuthenticationHandler CreateHandler() + => new OpenIddictValidationOwinHandler(_dispatcher, _factory); } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddlewareFactory.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddlewareFactory.cs index 2dd23351..79eb826b 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddlewareFactory.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinMiddlewareFactory.cs @@ -11,59 +11,58 @@ using Microsoft.Extensions.Options; using Microsoft.Owin; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.Owin +namespace OpenIddict.Validation.Owin; + +/// +/// Provides the entry point necessary to instantiate and register the scoped +/// in an OWIN/Katana pipeline. +/// +public class OpenIddictValidationOwinMiddlewareFactory : OwinMiddleware { /// - /// Provides the entry point necessary to instantiate and register the scoped - /// in an OWIN/Katana pipeline. + /// Creates a new instance of the class. /// - public class OpenIddictValidationOwinMiddlewareFactory : OwinMiddleware + /// The next middleware in the pipeline, if applicable. + public OpenIddictValidationOwinMiddlewareFactory(OwinMiddleware? next) + : base(next) { - /// - /// Creates a new instance of the class. - /// - /// The next middleware in the pipeline, if applicable. - public OpenIddictValidationOwinMiddlewareFactory(OwinMiddleware? next) - : base(next) + } + + /// + /// Resolves the instance from the OWIN context + /// and creates a new instance of the class, + /// which is used to register in the pipeline. + /// + /// The . + /// + /// A that can be used to monitor the asynchronous operation. + /// + public override Task Invoke(IOwinContext context) + { + if (context is null) { + throw new ArgumentNullException(nameof(context)); } - /// - /// Resolves the instance from the OWIN context - /// and creates a new instance of the class, - /// which is used to register in the pipeline. - /// - /// The . - /// - /// A that can be used to monitor the asynchronous operation. - /// - public override Task Invoke(IOwinContext context) + var provider = context.Get(typeof(IServiceProvider).FullName); + if (provider is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - var provider = context.Get(typeof(IServiceProvider).FullName); - if (provider is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0168)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0168)); + } - // Note: the Microsoft.Extensions.DependencyInjection container doesn't support resolving services - // with arbitrary parameters, which prevents the validation OWIN middleware from being resolved directly - // from the DI container, as the next middleware in the pipeline cannot be specified as a parameter. - // To work around this limitation, the validation OWIN middleware is manually instantiated and invoked. - var middleware = new OpenIddictValidationOwinMiddleware( - next: Next, - options: GetRequiredService>(provider), - dispatcher: GetRequiredService(provider), - factory: GetRequiredService(provider)); + // Note: the Microsoft.Extensions.DependencyInjection container doesn't support resolving services + // with arbitrary parameters, which prevents the validation OWIN middleware from being resolved directly + // from the DI container, as the next middleware in the pipeline cannot be specified as a parameter. + // To work around this limitation, the validation OWIN middleware is manually instantiated and invoked. + var middleware = new OpenIddictValidationOwinMiddleware( + next: Next, + options: GetRequiredService>(provider), + dispatcher: GetRequiredService(provider), + factory: GetRequiredService(provider)); - return middleware.Invoke(context); + return middleware.Invoke(context); - static T GetRequiredService(IServiceProvider provider) => provider.GetService() ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0169)); - } + static T GetRequiredService(IServiceProvider provider) => provider.GetService() ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0169)); } } diff --git a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinOptions.cs b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinOptions.cs index 4f4336ba..8f3ed263 100644 --- a/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinOptions.cs +++ b/src/OpenIddict.Validation.Owin/OpenIddictValidationOwinOptions.cs @@ -6,23 +6,22 @@ using Microsoft.Owin.Security; -namespace OpenIddict.Validation.Owin +namespace OpenIddict.Validation.Owin; + +/// +/// Provides various settings needed to configure the OpenIddict OWIN validation integration. +/// +public class OpenIddictValidationOwinOptions : AuthenticationOptions { /// - /// Provides various settings needed to configure the OpenIddict OWIN validation integration. + /// Creates a new instance of the class. /// - public class OpenIddictValidationOwinOptions : AuthenticationOptions - { - /// - /// Creates a new instance of the class. - /// - public OpenIddictValidationOwinOptions() - : base(OpenIddictValidationOwinDefaults.AuthenticationType) - => AuthenticationMode = AuthenticationMode.Passive; + public OpenIddictValidationOwinOptions() + : base(OpenIddictValidationOwinDefaults.AuthenticationType) + => AuthenticationMode = AuthenticationMode.Passive; - /// - /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. - /// - public string? Realm { get; set; } - } + /// + /// Gets or sets the optional "realm" value returned to the caller as part of the WWW-Authenticate header. + /// + public string? Realm { get; set; } } diff --git a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationBuilder.cs b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationBuilder.cs index ca39e5e8..23bf7d23 100644 --- a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationBuilder.cs +++ b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationBuilder.cs @@ -8,54 +8,53 @@ using System; using System.ComponentModel; using OpenIddict.Validation.ServerIntegration; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the OpenIddict validation services. +/// +public class OpenIddictValidationServerIntegrationBuilder { /// - /// Exposes the necessary methods required to configure the OpenIddict validation services. + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictValidationServerIntegrationBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. /// - public class OpenIddictValidationServerIntegrationBuilder + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Amends the default OpenIddict validation/server integration configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictValidationServerIntegrationBuilder Configure(Action configuration) { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictValidationServerIntegrationBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Amends the default OpenIddict validation/server integration configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictValidationServerIntegrationBuilder Configure(Action configuration) + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); - } + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs index b1c7483e..291578d6 100644 --- a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs +++ b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs @@ -10,88 +10,87 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; using OpenIddict.Server; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.ServerIntegration +namespace OpenIddict.Validation.ServerIntegration; + +/// +/// Contains the methods required to ensure that the OpenIddict validation/server integration configuration is valid. +/// +public class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions, + IPostConfigureOptions { + private readonly IOptionsMonitor _options; + /// - /// Contains the methods required to ensure that the OpenIddict validation/server integration configuration is valid. + /// Creates a new instance of the class. /// - public class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions, - IPostConfigureOptions - { - private readonly IOptionsMonitor _options; + /// The OpenIddict server options. + public OpenIddictValidationServerIntegrationConfiguration(IOptionsMonitor options) + => _options = options; - /// - /// Creates a new instance of the class. - /// - /// The OpenIddict server options. - public OpenIddictValidationServerIntegrationConfiguration(IOptionsMonitor options) - => _options = options; + /// + /// Populates the default OpenIddict validation/server integration options + /// and ensures that the configuration is in a consistent and valid state. + /// + /// The options instance to initialize. + public void Configure(OpenIddictValidationOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } - /// - /// Populates the default OpenIddict validation/server integration options - /// and ensures that the configuration is in a consistent and valid state. - /// - /// The options instance to initialize. - public void Configure(OpenIddictValidationOptions options) + // Note: the issuer may be null. In this case, it will be usually provided by + // a validation handler registered by the host (e.g ASP.NET Core or OWIN/Katana). + options.Configuration = new OpenIdConnectConfiguration { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + Issuer = _options.CurrentValue.Issuer?.AbsoluteUri + }; - // Note: the issuer may be null. In this case, it will be usually provided by - // a validation handler registered by the host (e.g ASP.NET Core or OWIN/Katana). - options.Configuration = new OpenIdConnectConfiguration - { - Issuer = _options.CurrentValue.Issuer?.AbsoluteUri - }; + // Import the signing keys from the server configuration. + foreach (var credentials in _options.CurrentValue.SigningCredentials) + { + options.Configuration.SigningKeys.Add(credentials.Key); + } - // Import the signing keys from the server configuration. - foreach (var credentials in _options.CurrentValue.SigningCredentials) - { - options.Configuration.SigningKeys.Add(credentials.Key); - } + // Import the encryption keys from the server configuration. + options.EncryptionCredentials.AddRange(_options.CurrentValue.EncryptionCredentials); - // Import the encryption keys from the server configuration. - options.EncryptionCredentials.AddRange(_options.CurrentValue.EncryptionCredentials); + // Note: token entry validation must be enabled to be able to validate reference access tokens. + options.EnableTokenEntryValidation = _options.CurrentValue.UseReferenceAccessTokens; + } - // Note: token entry validation must be enabled to be able to validate reference access tokens. - options.EnableTokenEntryValidation = _options.CurrentValue.UseReferenceAccessTokens; + /// + /// Populates the default OpenIddict validation/server integration options + /// and ensures that the configuration is in a consistent and valid state. + /// + /// The name of the options instance to configure, if applicable. + /// The options instance to initialize. + public void PostConfigure(string name, OpenIddictValidationOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); } - /// - /// Populates the default OpenIddict validation/server integration options - /// and ensures that the configuration is in a consistent and valid state. - /// - /// The name of the options instance to configure, if applicable. - /// The options instance to initialize. - public void PostConfigure(string name, OpenIddictValidationOptions options) + if (options.ValidationType != OpenIddictValidationType.Direct) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (options.ValidationType != OpenIddictValidationType.Direct) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0170)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0170)); + } - // Note: authorization validation requires that authorizations have an entry - // in the database (containing at least the authorization metadata), which is - // not created if the authorization storage is disabled in the server options. - if (options.EnableAuthorizationEntryValidation && _options.CurrentValue.DisableAuthorizationStorage) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0171)); - } + // Note: authorization validation requires that authorizations have an entry + // in the database (containing at least the authorization metadata), which is + // not created if the authorization storage is disabled in the server options. + if (options.EnableAuthorizationEntryValidation && _options.CurrentValue.DisableAuthorizationStorage) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0171)); + } - // Note: token validation requires that tokens have an entry in the database - // (containing at least the token metadata), which is not created if the - // token storage is disabled in the OpenIddict server options. - if (options.EnableTokenEntryValidation && _options.CurrentValue.DisableTokenStorage) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0172)); - } + // Note: token validation requires that tokens have an entry in the database + // (containing at least the token metadata), which is not created if the + // token storage is disabled in the OpenIddict server options. + if (options.EnableTokenEntryValidation && _options.CurrentValue.DisableTokenStorage) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0172)); } } } diff --git a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs index d21887b4..1f8549f5 100644 --- a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs +++ b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs @@ -10,61 +10,60 @@ using Microsoft.Extensions.Options; using OpenIddict.Validation; using OpenIddict.Validation.ServerIntegration; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict validation/server integration services. +/// +public static class OpenIddictValidationServerIntegrationExtensions { /// - /// Exposes extensions allowing to register the OpenIddict validation/server integration services. + /// Registers the OpenIddict validation/server integration services in the DI container + /// and automatically imports the configuration from the local OpenIddict server. /// - public static class OpenIddictValidationServerIntegrationExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationServerIntegrationBuilder UseLocalServer(this OpenIddictValidationBuilder builder) { - /// - /// Registers the OpenIddict validation/server integration services in the DI container - /// and automatically imports the configuration from the local OpenIddict server. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationServerIntegrationBuilder UseLocalServer(this OpenIddictValidationBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } - - // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. - builder.Services.TryAddEnumerable(new[] - { - ServiceDescriptor.Singleton, OpenIddictValidationServerIntegrationConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictValidationServerIntegrationConfiguration>() - }); - - return new OpenIddictValidationServerIntegrationBuilder(builder.Services); + throw new ArgumentNullException(nameof(builder)); } - /// - /// Registers the OpenIddict validation/server integration services in the DI container - /// and automatically imports the configuration from the local OpenIddict server. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the validation services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationBuilder UseLocalServer( - this OpenIddictValidationBuilder builder, Action configuration) + // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. + builder.Services.TryAddEnumerable(new[] { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + ServiceDescriptor.Singleton, OpenIddictValidationServerIntegrationConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictValidationServerIntegrationConfiguration>() + }); - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + return new OpenIddictValidationServerIntegrationBuilder(builder.Services); + } - configuration(builder.UseLocalServer()); + /// + /// Registers the OpenIddict validation/server integration services in the DI container + /// and automatically imports the configuration from the local OpenIddict server. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the validation services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationBuilder UseLocalServer( + this OpenIddictValidationBuilder builder, Action configuration) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } - return builder; + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); } + + configuration(builder.UseLocalServer()); + + return builder; } } diff --git a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationOptions.cs b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationOptions.cs index 787b6fcc..bfba1783 100644 --- a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationOptions.cs +++ b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationOptions.cs @@ -4,12 +4,11 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Validation.ServerIntegration +namespace OpenIddict.Validation.ServerIntegration; + +/// +/// Provides various settings needed to configure the OpenIddict validation/server integration. +/// +public class OpenIddictValidationServerIntegrationOptions { - /// - /// Provides various settings needed to configure the OpenIddict validation/server integration. - /// - public class OpenIddictValidationServerIntegrationOptions - { - } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs index 83db863a..d8e4044a 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs @@ -10,62 +10,61 @@ using System.Net.Http; using OpenIddict.Validation.SystemNetHttp; using Polly; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the OpenIddict validation/System.Net.Http integration. +/// +public class OpenIddictValidationSystemNetHttpBuilder { /// - /// Exposes the necessary methods required to configure the OpenIddict validation/System.Net.Http integration. + /// Initializes a new instance of . /// - public class OpenIddictValidationSystemNetHttpBuilder - { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictValidationSystemNetHttpBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); + /// The services collection. + public OpenIddictValidationSystemNetHttpBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } - /// - /// Amends the default OpenIddict validation/server integration configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictValidationSystemNetHttpBuilder Configure(Action configuration) + /// + /// Amends the default OpenIddict validation/server integration configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictValidationSystemNetHttpBuilder Configure(Action configuration) + { + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Replaces the default HTTP error policy used by the OpenIddict validation services. - /// - /// The HTTP Polly error policy. - /// The . - public OpenIddictValidationSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy policy) - => Configure(options => options.HttpErrorPolicy = policy); + /// + /// Replaces the default HTTP error policy used by the OpenIddict validation services. + /// + /// The HTTP Polly error policy. + /// The . + public OpenIddictValidationSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy policy) + => Configure(options => options.HttpErrorPolicy = policy); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); - } + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs index 234f370b..499c2171 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs @@ -11,69 +11,68 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; -namespace OpenIddict.Validation.SystemNetHttp +namespace OpenIddict.Validation.SystemNetHttp; + +/// +/// Contains the methods required to ensure that the OpenIddict validation/System.Net.Http integration configuration is valid. +/// +public class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions, + IConfigureNamedOptions { - /// - /// Contains the methods required to ensure that the OpenIddict validation/System.Net.Http integration configuration is valid. - /// - public class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions, - IConfigureNamedOptions - { #if !SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER - private readonly IServiceProvider _provider; + private readonly IServiceProvider _provider; - public OpenIddictValidationSystemNetHttpConfiguration(IServiceProvider provider) - => _provider = provider; + public OpenIddictValidationSystemNetHttpConfiguration(IServiceProvider provider) + => _provider = provider; #endif - public void Configure(OpenIddictValidationOptions options) + public void Configure(OpenIddictValidationOptions options) + { + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - // Register the built-in event handlers used by the OpenIddict System.Net.Http validation components. - options.Handlers.AddRange(OpenIddictValidationSystemNetHttpHandlers.DefaultHandlers); + throw new ArgumentNullException(nameof(options)); } - public void Configure(HttpClientFactoryOptions options) - => Debug.Fail("This infrastructure method shouldn't be called."); + // Register the built-in event handlers used by the OpenIddict System.Net.Http validation components. + options.Handlers.AddRange(OpenIddictValidationSystemNetHttpHandlers.DefaultHandlers); + } + + public void Configure(HttpClientFactoryOptions options) + => Debug.Fail("This infrastructure method shouldn't be called."); - public void Configure(string name, HttpClientFactoryOptions options) + public void Configure(string name, HttpClientFactoryOptions options) + { + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new ArgumentNullException(nameof(options)); + } - var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName(); + var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName(); - if (!string.Equals(name, assembly.Name, StringComparison.Ordinal)) - { - return; - } + if (!string.Equals(name, assembly.Name, StringComparison.Ordinal)) + { + return; + } - options.HttpClientActions.Add(client => - { - client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue( - productName: assembly.Name!, - productVersion: assembly.Version!.ToString())); - }); + options.HttpClientActions.Add(client => + { + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue( + productName: assembly.Name!, + productVersion: assembly.Version!.ToString())); + }); - options.HttpMessageHandlerBuilderActions.Add(builder => - { + options.HttpMessageHandlerBuilderActions.Add(builder => + { #if SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER - var options = builder.Services.GetRequiredService>(); + var options = builder.Services.GetRequiredService>(); #else - var options = _provider.GetRequiredService>(); + var options = _provider.GetRequiredService>(); #endif - var policy = options.CurrentValue.HttpErrorPolicy; - if (policy is not null) - { - builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy)); - } - }); - } + var policy = options.CurrentValue.HttpErrorPolicy; + if (policy is not null) + { + builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy)); + } + }); } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs index 2d23038d..cf73f64c 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs @@ -14,68 +14,67 @@ using OpenIddict.Validation.SystemNetHttp; using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlerFilters; using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlers; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes extensions allowing to register the OpenIddict validation/System.Net.Http integration services. +/// +public static class OpenIddictValidationSystemNetHttpExtensions { /// - /// Exposes extensions allowing to register the OpenIddict validation/System.Net.Http integration services. + /// Registers the OpenIddict validation/System.Net.Http integration services in the DI container. /// - public static class OpenIddictValidationSystemNetHttpExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationSystemNetHttpBuilder UseSystemNetHttp(this OpenIddictValidationBuilder builder) { - /// - /// Registers the OpenIddict validation/System.Net.Http integration services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationSystemNetHttpBuilder UseSystemNetHttp(this OpenIddictValidationBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddHttpClient(); + builder.Services.AddHttpClient(); - // Register the built-in validation event handlers used by the OpenIddict System.Net.Http components. - // Note: the order used here is not important, as the actual order is set in the options. - builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); + // Register the built-in validation event handlers used by the OpenIddict System.Net.Http components. + // Note: the order used here is not important, as the actual order is set in the options. + builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); - // Register the built-in filters used by the default OpenIddict System.Net.Http event handlers. - builder.Services.TryAddSingleton(); + // Register the built-in filters used by the default OpenIddict System.Net.Http event handlers. + builder.Services.TryAddSingleton(); - // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. - builder.Services.TryAddEnumerable(new[] - { - ServiceDescriptor.Singleton, OpenIddictValidationSystemNetHttpConfiguration>(), - ServiceDescriptor.Singleton, OpenIddictValidationSystemNetHttpConfiguration>() - }); + // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. + builder.Services.TryAddEnumerable(new[] + { + ServiceDescriptor.Singleton, OpenIddictValidationSystemNetHttpConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictValidationSystemNetHttpConfiguration>() + }); - return new OpenIddictValidationSystemNetHttpBuilder(builder.Services); - } + return new OpenIddictValidationSystemNetHttpBuilder(builder.Services); + } - /// - /// Registers the OpenIddict validation/System.Net.Http integration services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the validation services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationBuilder UseSystemNetHttp( - this OpenIddictValidationBuilder builder, Action configuration) + /// + /// Registers the OpenIddict validation/System.Net.Http integration services in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the validation services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationBuilder UseSystemNetHttp( + this OpenIddictValidationBuilder builder, Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.UseSystemNetHttp()); + configuration(builder.UseSystemNetHttp()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlerFilters.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlerFilters.cs index 1a8bcf6d..7a004f13 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlerFilters.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlerFilters.cs @@ -9,27 +9,26 @@ using System.ComponentModel; using System.Threading.Tasks; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace OpenIddict.Validation.SystemNetHttp +namespace OpenIddict.Validation.SystemNetHttp; + +[EditorBrowsable(EditorBrowsableState.Advanced)] +public static class OpenIddictValidationSystemNetHttpHandlerFilters { - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static class OpenIddictValidationSystemNetHttpHandlerFilters + /// + /// Represents a filter that excludes the associated handlers if the metadata address of the issuer is not available. + /// + public class RequireHttpMetadataAddress : IOpenIddictValidationHandlerFilter { - /// - /// Represents a filter that excludes the associated handlers if the metadata address of the issuer is not available. - /// - public class RequireHttpMetadataAddress : IOpenIddictValidationHandlerFilter + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask( - string.Equals(context.Options.MetadataAddress?.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) || - string.Equals(context.Options.MetadataAddress?.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask( + string.Equals(context.Options.MetadataAddress?.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) || + string.Equals(context.Options.MetadataAddress?.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)); } } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs index 41c97de5..5df16d46 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Discovery.cs @@ -7,40 +7,39 @@ using System.Collections.Immutable; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace OpenIddict.Validation.SystemNetHttp +namespace OpenIddict.Validation.SystemNetHttp; + +public static partial class OpenIddictValidationSystemNetHttpHandlers { - public static partial class OpenIddictValidationSystemNetHttpHandlers + public static class Discovery { - public static class Discovery - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Configuration request processing: - */ - PrepareGetHttpRequest.Descriptor, - AttachQueryStringParameters.Descriptor, - SendHttpRequest.Descriptor, - DisposeHttpRequest.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Configuration request processing: + */ + PrepareGetHttpRequest.Descriptor, + AttachQueryStringParameters.Descriptor, + SendHttpRequest.Descriptor, + DisposeHttpRequest.Descriptor, - /* - * Configuration response processing: - */ - ExtractJsonHttpResponse.Descriptor, - DisposeHttpResponse.Descriptor, + /* + * Configuration response processing: + */ + ExtractJsonHttpResponse.Descriptor, + DisposeHttpResponse.Descriptor, - /* - * Cryptography request processing: - */ - PrepareGetHttpRequest.Descriptor, - AttachQueryStringParameters.Descriptor, - SendHttpRequest.Descriptor, - DisposeHttpRequest.Descriptor, + /* + * Cryptography request processing: + */ + PrepareGetHttpRequest.Descriptor, + AttachQueryStringParameters.Descriptor, + SendHttpRequest.Descriptor, + DisposeHttpRequest.Descriptor, - /* - * Configuration response processing: - */ - ExtractJsonHttpResponse.Descriptor, - DisposeHttpResponse.Descriptor); - } + /* + * Configuration response processing: + */ + ExtractJsonHttpResponse.Descriptor, + DisposeHttpResponse.Descriptor); } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs index 3dc2b196..71ddcb97 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.Introspection.cs @@ -16,101 +16,100 @@ using static OpenIddict.Validation.OpenIddictValidationEvents; using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.SystemNetHttp +namespace OpenIddict.Validation.SystemNetHttp; + +public static partial class OpenIddictValidationSystemNetHttpHandlers { - public static partial class OpenIddictValidationSystemNetHttpHandlers + public static class Introspection { - public static class Introspection - { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Introspection request processing: - */ - PreparePostHttpRequest.Descriptor, - AttachBasicAuthenticationCredentials.Descriptor, - AttachFormParameters.Descriptor, - SendHttpRequest.Descriptor, - DisposeHttpRequest.Descriptor, + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Introspection request processing: + */ + PreparePostHttpRequest.Descriptor, + AttachBasicAuthenticationCredentials.Descriptor, + AttachFormParameters.Descriptor, + SendHttpRequest.Descriptor, + DisposeHttpRequest.Descriptor, - /* - * Introspection response processing: - */ - ExtractJsonHttpResponse.Descriptor, - DisposeHttpResponse.Descriptor); + /* + * Introspection response processing: + */ + ExtractJsonHttpResponse.Descriptor, + DisposeHttpResponse.Descriptor); + /// + /// Contains the logic responsible of attaching the client credentials to the HTTP Authorization header. + /// + public class AttachBasicAuthenticationCredentials : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of attaching the client credentials to the HTTP Authorization header. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachBasicAuthenticationCredentials : IOpenIddictValidationHandler - { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(AttachFormParameters.Descriptor.Order - 1000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(AttachFormParameters.Descriptor.Order - 1000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); - /// - public async ValueTask HandleAsync(PrepareIntrospectionRequestContext context) + /// + public async ValueTask HandleAsync(PrepareIntrospectionRequestContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, - // this may indicate that the request was incorrectly processed by another client stack. - var request = context.Transaction.GetHttpRequestMessage(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); - } + Debug.Assert(context.Request is not null, SR.GetResourceString(SR.ID4008)); - // If no client identifier was attached to the request, skip the following logic. - if (string.IsNullOrEmpty(context.Request.ClientId)) - { - return; - } + // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, + // this may indicate that the request was incorrectly processed by another client stack. + var request = context.Transaction.GetHttpRequestMessage(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); + } - var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + // If no client identifier was attached to the request, skip the following logic. + if (string.IsNullOrEmpty(context.Request.ClientId)) + { + return; + } - // The OAuth 2.0 specification recommends sending the client credentials using basic authentication. - // However, this authentication method is known to have compatibility issues with the way the - // client credentials are encoded (they MUST be formURL-encoded before being base64-encoded). - // To guarantee that the OpenIddict validation handler can be used with servers implementing - // non-standard encoding, the client_secret_post is always preferred when it's explicitly - // listed as a supported client authentication method for the introspection endpoint. - // If client_secret_post is not listed or if the server returned an empty methods list, - // client_secret_basic is always used, as it MUST be implemented by all OAuth 2.0 servers. - // - // See https://tools.ietf.org/html/rfc8414#section-2 - // and https://tools.ietf.org/html/rfc6749#section-2.3.1 for more information. - if (!configuration.IntrospectionEndpointAuthMethodsSupported.Contains(ClientAuthenticationMethods.ClientSecretPost)) - { - // Important: the credentials MUST be formURL-encoded before being base64-encoded. - var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(new StringBuilder() - .Append(EscapeDataString(context.Request.ClientId)) - .Append(':') - .Append(EscapeDataString(context.Request.ClientSecret)) - .ToString())); + var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); - // Attach the authorization header containing the client credentials to the HTTP request. - request.Headers.Authorization = new AuthenticationHeaderValue(Schemes.Basic, credentials); + // The OAuth 2.0 specification recommends sending the client credentials using basic authentication. + // However, this authentication method is known to have compatibility issues with the way the + // client credentials are encoded (they MUST be formURL-encoded before being base64-encoded). + // To guarantee that the OpenIddict validation handler can be used with servers implementing + // non-standard encoding, the client_secret_post is always preferred when it's explicitly + // listed as a supported client authentication method for the introspection endpoint. + // If client_secret_post is not listed or if the server returned an empty methods list, + // client_secret_basic is always used, as it MUST be implemented by all OAuth 2.0 servers. + // + // See https://tools.ietf.org/html/rfc8414#section-2 + // and https://tools.ietf.org/html/rfc6749#section-2.3.1 for more information. + if (!configuration.IntrospectionEndpointAuthMethodsSupported.Contains(ClientAuthenticationMethods.ClientSecretPost)) + { + // Important: the credentials MUST be formURL-encoded before being base64-encoded. + var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(new StringBuilder() + .Append(EscapeDataString(context.Request.ClientId)) + .Append(':') + .Append(EscapeDataString(context.Request.ClientSecret)) + .ToString())); - // Remove the client credentials from the request payload to ensure they are not sent twice. - context.Request.ClientId = context.Request.ClientSecret = null; - } + // Attach the authorization header containing the client credentials to the HTTP request. + request.Headers.Authorization = new AuthenticationHeaderValue(Schemes.Basic, credentials); - static string? EscapeDataString(string? value) - => value is not null ? Uri.EscapeDataString(value).Replace("%20", "+") : null; + // Remove the client credentials from the request payload to ensure they are not sent twice. + context.Request.ClientId = context.Request.ClientSecret = null; } + + static string? EscapeDataString(string? value) + => value is not null ? Uri.EscapeDataString(value).Replace("%20", "+") : null; } } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs index dac46c1a..21c64aec 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs @@ -21,389 +21,388 @@ using static OpenIddict.Validation.OpenIddictValidationEvents; using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation.SystemNetHttp +namespace OpenIddict.Validation.SystemNetHttp; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class OpenIddictValidationSystemNetHttpHandlers { - [EditorBrowsable(EditorBrowsableState.Never)] - public static partial class OpenIddictValidationSystemNetHttpHandlers + public static ImmutableArray DefaultHandlers { get; } + = ImmutableArray.Create() + .AddRange(Discovery.DefaultHandlers) + .AddRange(Introspection.DefaultHandlers); + + /// + /// Contains the logic responsible of preparing an HTTP GET request message. + /// + public class PrepareGetHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext { - public static ImmutableArray DefaultHandlers { get; } - = ImmutableArray.Create() - .AddRange(Discovery.DefaultHandlers) - .AddRange(Introspection.DefaultHandlers); - /// - /// Contains the logic responsible of preparing an HTTP GET request message. + /// Gets the default descriptor definition assigned to this handler. /// - public class PrepareGetHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The HTTP request message is disposed later by a dedicated handler.")] + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The HTTP request message is disposed later by a dedicated handler.")] - public ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var request = new HttpRequestMessage(HttpMethod.Get, context.Address) + var request = new HttpRequestMessage(HttpMethod.Get, context.Address) + { + Headers = { - Headers = - { - Accept = { new MediaTypeWithQualityHeaderValue("application/json") }, - AcceptCharset = { new StringWithQualityHeaderValue("utf-8") } - } - }; + Accept = { new MediaTypeWithQualityHeaderValue("application/json") }, + AcceptCharset = { new StringWithQualityHeaderValue("utf-8") } + } + }; - // Store the HttpRequestMessage in the transaction properties. - context.Transaction.SetProperty(typeof(HttpRequestMessage).FullName!, request); + // Store the HttpRequestMessage in the transaction properties. + context.Transaction.SetProperty(typeof(HttpRequestMessage).FullName!, request); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of preparing an HTTP POST request message. + /// + public class PreparePostHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext + { /// - /// Contains the logic responsible of preparing an HTTP POST request message. + /// Gets the default descriptor definition assigned to this handler. /// - public class PreparePostHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(PrepareGetHttpRequest.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The HTTP request message is disposed later by a dedicated handler.")] + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(PrepareGetHttpRequest.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The HTTP request message is disposed later by a dedicated handler.")] - public ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var request = new HttpRequestMessage(HttpMethod.Post, context.Address) + var request = new HttpRequestMessage(HttpMethod.Post, context.Address) + { + Headers = { - Headers = - { - Accept = { new MediaTypeWithQualityHeaderValue("application/json") }, - AcceptCharset = { new StringWithQualityHeaderValue("utf-8") } - } - }; + Accept = { new MediaTypeWithQualityHeaderValue("application/json") }, + AcceptCharset = { new StringWithQualityHeaderValue("utf-8") } + } + }; - // Store the HttpRequestMessage in the transaction properties. - context.Transaction.SetProperty(typeof(HttpRequestMessage).FullName!, request); + // Store the HttpRequestMessage in the transaction properties. + context.Transaction.SetProperty(typeof(HttpRequestMessage).FullName!, request); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the query string parameters to the HTTP request. + /// + public class AttachQueryStringParameters : IOpenIddictValidationHandler where TContext : BaseExternalContext + { /// - /// Contains the logic responsible of attaching the query string parameters to the HTTP request. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachQueryStringParameters : IOpenIddictValidationHandler where TContext : BaseExternalContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(AttachFormParameters.Descriptor.Order - 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(AttachFormParameters.Descriptor.Order - 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); - Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); + Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); + Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); - // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, - // this may indicate that the request was incorrectly processed by another client stack. - var request = context.Transaction.GetHttpRequestMessage(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); - } + // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, + // this may indicate that the request was incorrectly processed by another client stack. + var request = context.Transaction.GetHttpRequestMessage(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); + } - if (request.RequestUri is null || context.Transaction.Request.Count == 0) - { - return default; - } + if (request.RequestUri is null || context.Transaction.Request.Count == 0) + { + return default; + } - var builder = new StringBuilder(); + var builder = new StringBuilder(); - foreach (var (key, value) in - from parameter in context.Transaction.Request.GetParameters() - let values = (string?[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select (parameter.Key, Value: value)) + foreach (var (key, value) in + from parameter in context.Transaction.Request.GetParameters() + let values = (string?[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select (parameter.Key, Value: value)) + { + if (builder.Length > 0) { - if (builder.Length > 0) - { - builder.Append('&'); - } - - builder.Append(Uri.EscapeDataString(key)); - builder.Append('='); - builder.Append(Uri.EscapeDataString(value)); + builder.Append('&'); } - // Compute the final request URI using the base address and the query string. - request.RequestUri = new UriBuilder(request.RequestUri) { Query = builder.ToString() }.Uri; - - return default; + builder.Append(Uri.EscapeDataString(key)); + builder.Append('='); + builder.Append(Uri.EscapeDataString(value)); } + + // Compute the final request URI using the base address and the query string. + request.RequestUri = new UriBuilder(request.RequestUri) { Query = builder.ToString() }.Uri; + + return default; } + } + /// + /// Contains the logic responsible of attaching the form parameters to the HTTP request. + /// + public class AttachFormParameters : IOpenIddictValidationHandler where TContext : BaseExternalContext + { /// - /// Contains the logic responsible of attaching the form parameters to the HTTP request. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachFormParameters : IOpenIddictValidationHandler where TContext : BaseExternalContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); + Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); - // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, - // this may indicate that the request was incorrectly processed by another client stack. - var request = context.Transaction.GetHttpRequestMessage(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); - } + // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, + // this may indicate that the request was incorrectly processed by another client stack. + var request = context.Transaction.GetHttpRequestMessage(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); + } - request.Content = new FormUrlEncodedContent( - from parameter in context.Transaction.Request.GetParameters() - let values = (string[]?) parameter.Value - where values is not null - from value in values - where !string.IsNullOrEmpty(value) - select new KeyValuePair(parameter.Key, value)); + request.Content = new FormUrlEncodedContent( + from parameter in context.Transaction.Request.GetParameters() + let values = (string[]?) parameter.Value + where values is not null + from value in values + where !string.IsNullOrEmpty(value) + select new KeyValuePair(parameter.Key, value)); - return default; - } + return default; } + } + + /// + /// Contains the logic responsible of sending the HTTP request to the remote server. + /// + public class SendHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext + { + private readonly IHttpClientFactory _factory; + + public SendHttpRequest(IHttpClientFactory factory) + => _factory = factory; /// - /// Contains the logic responsible of sending the HTTP request to the remote server. + /// Gets the default descriptor definition assigned to this handler. /// - public class SendHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(DisposeHttpRequest.Descriptor.Order - 50_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - private readonly IHttpClientFactory _factory; - - public SendHttpRequest(IHttpClientFactory factory) - => _factory = factory; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(DisposeHttpRequest.Descriptor.Order - 50_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, - // this may indicate that the request was incorrectly processed by another client stack. - var request = context.Transaction.GetHttpRequestMessage(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); - } + throw new ArgumentNullException(nameof(context)); + } - var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName(); - using var client = _factory.CreateClient(assembly.Name); - if (client is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0174)); - } + // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, + // this may indicate that the request was incorrectly processed by another client stack. + var request = context.Transaction.GetHttpRequestMessage(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); + } - var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead); - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0175)); - } + var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName(); + using var client = _factory.CreateClient(assembly.Name); + if (client is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0174)); + } - // Store the HttpResponseMessage in the transaction properties. - context.Transaction.SetProperty(typeof(HttpResponseMessage).FullName!, response); + var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead); + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0175)); } + + // Store the HttpResponseMessage in the transaction properties. + context.Transaction.SetProperty(typeof(HttpResponseMessage).FullName!, response); } + } + /// + /// Contains the logic responsible of disposing of the HTTP request message. + /// + public class DisposeHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext + { /// - /// Contains the logic responsible of disposing of the HTTP request message. + /// Gets the default descriptor definition assigned to this handler. /// - public class DisposeHttpRequest : IOpenIddictValidationHandler where TContext : BaseExternalContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, - // this may indicate that the request was incorrectly processed by another client stack. - var request = context.Transaction.GetHttpRequestMessage(); - if (request is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); - } + // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved, + // this may indicate that the request was incorrectly processed by another client stack. + var request = context.Transaction.GetHttpRequestMessage(); + if (request is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); + } - request.Dispose(); + request.Dispose(); - // Remove the request from the transaction properties. - context.Transaction.SetProperty(typeof(HttpRequestMessage).FullName!, null); + // Remove the request from the transaction properties. + context.Transaction.SetProperty(typeof(HttpRequestMessage).FullName!, null); - return default; - } + return default; } + } + /// + /// Contains the logic responsible of extracting the response from the JSON-encoded HTTP body. + /// + public class ExtractJsonHttpResponse : IOpenIddictValidationHandler where TContext : BaseExternalContext + { /// - /// Contains the logic responsible of extracting the response from the JSON-encoded HTTP body. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractJsonHttpResponse : IOpenIddictValidationHandler where TContext : BaseExternalContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(DisposeHttpResponse.Descriptor.Order - 50_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(DisposeHttpResponse.Descriptor.Order - 50_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to System.Net.Http requests. If the HTTP response cannot be resolved, - // this may indicate that the request was incorrectly processed by another client stack. - var response = context.Transaction.GetHttpResponseMessage(); - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); - } + // This handler only applies to System.Net.Http requests. If the HTTP response cannot be resolved, + // this may indicate that the request was incorrectly processed by another client stack. + var response = context.Transaction.GetHttpResponseMessage(); + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); + } - // The status code is deliberately not validated to ensure even errored responses - // (typically in the 4xx range) can be deserialized and handled by the event handlers. + // The status code is deliberately not validated to ensure even errored responses + // (typically in the 4xx range) can be deserialized and handled by the event handlers. - // Note: ReadFromJsonAsync() automatically validates the content type and the content encoding - // and transcode the response stream if a non-UTF-8 response is returned by the remote server. - context.Transaction.Response = await response.Content.ReadFromJsonAsync(); - } + // Note: ReadFromJsonAsync() automatically validates the content type and the content encoding + // and transcode the response stream if a non-UTF-8 response is returned by the remote server. + context.Transaction.Response = await response.Content.ReadFromJsonAsync(); } + } + /// + /// Contains the logic responsible of disposing of the HTTP response message. + /// + public class DisposeHttpResponse : IOpenIddictValidationHandler where TContext : BaseExternalContext + { /// - /// Contains the logic responsible of disposing of the HTTP response message. + /// Gets the default descriptor definition assigned to this handler. /// - public class DisposeHttpResponse : IOpenIddictValidationHandler where TContext : BaseExternalContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler>() + .SetOrder(int.MaxValue - 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler>() - .SetOrder(int.MaxValue - 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // This handler only applies to System.Net.Http requests. If the HTTP response cannot be resolved, - // this may indicate that the request was incorrectly processed by another client stack. - var response = context.Transaction.GetHttpResponseMessage(); - if (response is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); - } + // This handler only applies to System.Net.Http requests. If the HTTP response cannot be resolved, + // this may indicate that the request was incorrectly processed by another client stack. + var response = context.Transaction.GetHttpResponseMessage(); + if (response is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0173)); + } - response.Dispose(); + response.Dispose(); - // Remove the response from the transaction properties. - context.Transaction.SetProperty(typeof(HttpResponseMessage).FullName!, null); + // Remove the response from the transaction properties. + context.Transaction.SetProperty(typeof(HttpResponseMessage).FullName!, null); - return default; - } + return default; } } } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHelpers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHelpers.cs index 40bbbd1f..77f9ea84 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHelpers.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHelpers.cs @@ -6,27 +6,26 @@ using OpenIddict.Validation; -namespace System.Net.Http +namespace System.Net.Http; + +/// +/// Exposes companion extensions for the OpenIddict/System.Net.Http integration. +/// +public static class OpenIddictValidationSystemNetHttpHelpers { /// - /// Exposes companion extensions for the OpenIddict/System.Net.Http integration. + /// Gets the associated with the current context. /// - public static class OpenIddictValidationSystemNetHttpHelpers - { - /// - /// Gets the associated with the current context. - /// - /// The transaction instance. - /// The instance or null if it couldn't be found. - public static HttpRequestMessage? GetHttpRequestMessage(this OpenIddictValidationTransaction transaction) - => transaction.GetProperty(typeof(HttpRequestMessage).FullName!); + /// The transaction instance. + /// The instance or null if it couldn't be found. + public static HttpRequestMessage? GetHttpRequestMessage(this OpenIddictValidationTransaction transaction) + => transaction.GetProperty(typeof(HttpRequestMessage).FullName!); - /// - /// Gets the associated with the current context. - /// - /// The transaction instance. - /// The instance or null if it couldn't be found. - public static HttpResponseMessage? GetHttpResponseMessage(this OpenIddictValidationTransaction transaction) - => transaction.GetProperty(typeof(HttpResponseMessage).FullName!); - } + /// + /// Gets the associated with the current context. + /// + /// The transaction instance. + /// The instance or null if it couldn't be found. + public static HttpResponseMessage? GetHttpResponseMessage(this OpenIddictValidationTransaction transaction) + => transaction.GetProperty(typeof(HttpResponseMessage).FullName!); } diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs index c2c32338..6bf184e5 100644 --- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs +++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs @@ -10,19 +10,18 @@ using System.Net.Http; using Polly; using Polly.Extensions.Http; -namespace OpenIddict.Validation.SystemNetHttp +namespace OpenIddict.Validation.SystemNetHttp; + +/// +/// Provides various settings needed to configure the OpenIddict validation/System.Net.Http integration. +/// +public class OpenIddictValidationSystemNetHttpOptions { /// - /// Provides various settings needed to configure the OpenIddict validation/System.Net.Http integration. + /// Gets or sets the HTTP Polly error policy used by the internal OpenIddict HTTP clients. /// - public class OpenIddictValidationSystemNetHttpOptions - { - /// - /// Gets or sets the HTTP Polly error policy used by the internal OpenIddict HTTP clients. - /// - public IAsyncPolicy? HttpErrorPolicy { get; set; } - = HttpPolicyExtensions.HandleTransientHttpError() - .OrResult(response => response.StatusCode == HttpStatusCode.NotFound) - .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); - } + public IAsyncPolicy? HttpErrorPolicy { get; set; } + = HttpPolicyExtensions.HandleTransientHttpError() + .OrResult(response => response.StatusCode == HttpStatusCode.NotFound) + .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); } diff --git a/src/OpenIddict.Validation/IOpenIddictValidationDispatcher.cs b/src/OpenIddict.Validation/IOpenIddictValidationDispatcher.cs index 82189d05..1fe1a08b 100644 --- a/src/OpenIddict.Validation/IOpenIddictValidationDispatcher.cs +++ b/src/OpenIddict.Validation/IOpenIddictValidationDispatcher.cs @@ -7,10 +7,9 @@ using System.Threading.Tasks; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public interface IOpenIddictValidationDispatcher { - public interface IOpenIddictValidationDispatcher - { - ValueTask DispatchAsync(TContext context) where TContext : BaseContext; - } -} \ No newline at end of file + ValueTask DispatchAsync(TContext context) where TContext : BaseContext; +} diff --git a/src/OpenIddict.Validation/IOpenIddictValidationFactory.cs b/src/OpenIddict.Validation/IOpenIddictValidationFactory.cs index 4ba1a4a4..2cbdcd90 100644 --- a/src/OpenIddict.Validation/IOpenIddictValidationFactory.cs +++ b/src/OpenIddict.Validation/IOpenIddictValidationFactory.cs @@ -6,10 +6,9 @@ using System.Threading.Tasks; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public interface IOpenIddictValidationFactory { - public interface IOpenIddictValidationFactory - { - ValueTask CreateTransactionAsync(); - } -} \ No newline at end of file + ValueTask CreateTransactionAsync(); +} diff --git a/src/OpenIddict.Validation/IOpenIddictValidationHandler.cs b/src/OpenIddict.Validation/IOpenIddictValidationHandler.cs index ab78fef9..920f9f27 100644 --- a/src/OpenIddict.Validation/IOpenIddictValidationHandler.cs +++ b/src/OpenIddict.Validation/IOpenIddictValidationHandler.cs @@ -7,21 +7,20 @@ using System.Threading.Tasks; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Represents a handler able to process events. +/// +/// The type of the context associated with events handled by this instance. +public interface IOpenIddictValidationHandler where TContext : BaseContext { /// - /// Represents a handler able to process events. + /// Processes the event. /// - /// The type of the context associated with events handled by this instance. - public interface IOpenIddictValidationHandler where TContext : BaseContext - { - /// - /// Processes the event. - /// - /// The context associated with the event to process. - /// - /// A that can be used to monitor the asynchronous operation. - /// - ValueTask HandleAsync(TContext context); - } + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + ValueTask HandleAsync(TContext context); } diff --git a/src/OpenIddict.Validation/IOpenIddictValidationHandlerFilter.cs b/src/OpenIddict.Validation/IOpenIddictValidationHandlerFilter.cs index 28a71bff..877848bd 100644 --- a/src/OpenIddict.Validation/IOpenIddictValidationHandlerFilter.cs +++ b/src/OpenIddict.Validation/IOpenIddictValidationHandlerFilter.cs @@ -7,10 +7,9 @@ using System.Threading.Tasks; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public interface IOpenIddictValidationHandlerFilter where TContext : BaseContext { - public interface IOpenIddictValidationHandlerFilter where TContext : BaseContext - { - ValueTask IsActiveAsync(TContext context); - } + ValueTask IsActiveAsync(TContext context); } diff --git a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs index f137a398..b91f8ff5 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs @@ -18,492 +18,491 @@ using Microsoft.IdentityModel.Tokens; using OpenIddict.Validation; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Exposes the necessary methods required to configure the OpenIddict validation services. +/// +public class OpenIddictValidationBuilder { /// - /// Exposes the necessary methods required to configure the OpenIddict validation services. + /// Initializes a new instance of . + /// + /// The services collection. + public OpenIddictValidationBuilder(IServiceCollection services) + => Services = services ?? throw new ArgumentNullException(nameof(services)); + + /// + /// Gets the services collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IServiceCollection Services { get; } + + /// + /// Registers an event handler using the specified configuration delegate. /// - public class OpenIddictValidationBuilder + /// The event context type. + /// The configuration delegate. + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictValidationBuilder AddEventHandler( + Action> configuration) + where TContext : OpenIddictValidationEvents.BaseContext { - /// - /// Initializes a new instance of . - /// - /// The services collection. - public OpenIddictValidationBuilder(IServiceCollection services) - => Services = services ?? throw new ArgumentNullException(nameof(services)); - - /// - /// Gets the services collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IServiceCollection Services { get; } - - /// - /// Registers an event handler using the specified configuration delegate. - /// - /// The event context type. - /// The configuration delegate. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictValidationBuilder AddEventHandler( - Action> configuration) - where TContext : OpenIddictValidationEvents.BaseContext + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - // Note: handlers registered using this API are assumed to be custom handlers by default. - var builder = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .SetType(OpenIddictValidationHandlerType.Custom); + // Note: handlers registered using this API are assumed to be custom handlers by default. + var builder = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .SetType(OpenIddictValidationHandlerType.Custom); - configuration(builder); + configuration(builder); - return AddEventHandler(builder.Build()); - } + return AddEventHandler(builder.Build()); + } - /// - /// Registers an event handler using the specified descriptor. - /// - /// The handler descriptor. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictValidationBuilder AddEventHandler(OpenIddictValidationHandlerDescriptor descriptor) + /// + /// Registers an event handler using the specified descriptor. + /// + /// The handler descriptor. + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictValidationBuilder AddEventHandler(OpenIddictValidationHandlerDescriptor descriptor) + { + if (descriptor is null) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - // Register the handler in the services collection. - Services.Add(descriptor.ServiceDescriptor); + // Register the handler in the services collection. + Services.Add(descriptor.ServiceDescriptor); - return Configure(options => options.Handlers.Add(descriptor)); - } + return Configure(options => options.Handlers.Add(descriptor)); + } - /// - /// Removes the event handler that matches the specified descriptor. - /// - /// The descriptor corresponding to the handler to remove. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictValidationBuilder RemoveEventHandler(OpenIddictValidationHandlerDescriptor descriptor) + /// + /// Removes the event handler that matches the specified descriptor. + /// + /// The descriptor corresponding to the handler to remove. + /// The . + [EditorBrowsable(EditorBrowsableState.Advanced)] + public OpenIddictValidationBuilder RemoveEventHandler(OpenIddictValidationHandlerDescriptor descriptor) + { + if (descriptor is null) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - Services.RemoveAll(descriptor.ServiceDescriptor.ServiceType); + Services.RemoveAll(descriptor.ServiceDescriptor.ServiceType); - Services.PostConfigure(options => + Services.PostConfigure(options => + { + for (var index = options.Handlers.Count - 1; index >= 0; index--) { - for (var index = options.Handlers.Count - 1; index >= 0; index--) + if (options.Handlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType) { - if (options.Handlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType) - { - options.Handlers.RemoveAt(index); - } + options.Handlers.RemoveAt(index); } - }); + } + }); - return this; - } + return this; + } - /// - /// Amends the default OpenIddict validation configuration. - /// - /// The delegate used to configure the OpenIddict options. - /// This extension can be safely called multiple times. - /// The . - public OpenIddictValidationBuilder Configure(Action configuration) + /// + /// Amends the default OpenIddict validation configuration. + /// + /// The delegate used to configure the OpenIddict options. + /// This extension can be safely called multiple times. + /// The . + public OpenIddictValidationBuilder Configure(Action configuration) + { + if (configuration is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + throw new ArgumentNullException(nameof(configuration)); + } - Services.Configure(configuration); + Services.Configure(configuration); - return this; - } + return this; + } - /// - /// Registers encryption credentials. - /// - /// The encrypting credentials. - /// The . - public OpenIddictValidationBuilder AddEncryptionCredentials(EncryptingCredentials credentials) + /// + /// Registers encryption credentials. + /// + /// The encrypting credentials. + /// The . + public OpenIddictValidationBuilder AddEncryptionCredentials(EncryptingCredentials credentials) + { + if (credentials is null) { - if (credentials is null) - { - throw new ArgumentNullException(nameof(credentials)); - } + throw new ArgumentNullException(nameof(credentials)); + } + + return Configure(options => options.EncryptionCredentials.Add(credentials)); + } - return Configure(options => options.EncryptionCredentials.Add(credentials)); + /// + /// Registers an encryption key. + /// + /// The security key. + /// The . + public OpenIddictValidationBuilder AddEncryptionKey(SecurityKey key) + { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); } - /// - /// Registers an encryption key. - /// - /// The security key. - /// The . - public OpenIddictValidationBuilder AddEncryptionKey(SecurityKey key) + // If the encryption key is an asymmetric security key, ensure it has a private key. + if (key is AsymmetricSecurityKey asymmetricSecurityKey && + asymmetricSecurityKey.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist) { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0055)); + } - // If the encryption key is an asymmetric security key, ensure it has a private key. - if (key is AsymmetricSecurityKey asymmetricSecurityKey && - asymmetricSecurityKey.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0055)); - } + if (key.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)) + { + return AddEncryptionCredentials(new EncryptingCredentials(key, + SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)); + } - if (key.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)) - { - return AddEncryptionCredentials(new EncryptingCredentials(key, - SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)); - } + if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaOAEP)) + { + return AddEncryptionCredentials(new EncryptingCredentials(key, + SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512)); + } - if (key.IsSupportedAlgorithm(SecurityAlgorithms.RsaOAEP)) - { - return AddEncryptionCredentials(new EncryptingCredentials(key, - SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0056)); + } - throw new InvalidOperationException(SR.GetResourceString(SR.ID0056)); + /// + /// Registers an encryption certificate. + /// + /// The encryption certificate. + /// The . + public OpenIddictValidationBuilder AddEncryptionCertificate(X509Certificate2 certificate) + { + if (certificate is null) + { + throw new ArgumentNullException(nameof(certificate)); } - /// - /// Registers an encryption certificate. - /// - /// The encryption certificate. - /// The . - public OpenIddictValidationBuilder AddEncryptionCertificate(X509Certificate2 certificate) + // If the certificate is a X.509v3 certificate that specifies at least one + // key usage, ensure that the certificate key can be used for key encryption. + if (certificate.Version >= 3) { - if (certificate is null) + var extensions = certificate.Extensions.OfType().ToList(); + if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.KeyEncipherment))) { - throw new ArgumentNullException(nameof(certificate)); - } - - // If the certificate is a X.509v3 certificate that specifies at least one - // key usage, ensure that the certificate key can be used for key encryption. - if (certificate.Version >= 3) - { - var extensions = certificate.Extensions.OfType().ToList(); - if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.KeyEncipherment))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0060)); - } - } - - if (!certificate.HasPrivateKey) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0061)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0060)); } + } - return AddEncryptionKey(new X509SecurityKey(certificate)); + if (!certificate.HasPrivateKey) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0061)); } - /// - /// Registers an encryption certificate retrieved from an embedded resource. - /// - /// The assembly containing the certificate. - /// The name of the embedded resource. - /// The password used to open the certificate. - /// The . - public OpenIddictValidationBuilder AddEncryptionCertificate( - Assembly assembly, string resource, string? password) + return AddEncryptionKey(new X509SecurityKey(certificate)); + } + + /// + /// Registers an encryption certificate retrieved from an embedded resource. + /// + /// The assembly containing the certificate. + /// The name of the embedded resource. + /// The password used to open the certificate. + /// The . + public OpenIddictValidationBuilder AddEncryptionCertificate( + Assembly assembly, string resource, string? password) #if SUPPORTS_EPHEMERAL_KEY_SETS - // Note: ephemeral key sets are currently not supported on macOS. - => AddEncryptionCertificate(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? - X509KeyStorageFlags.MachineKeySet : - X509KeyStorageFlags.EphemeralKeySet); + // Note: ephemeral key sets are currently not supported on macOS. + => AddEncryptionCertificate(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + X509KeyStorageFlags.MachineKeySet : + X509KeyStorageFlags.EphemeralKeySet); #else - => AddEncryptionCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet); + => AddEncryptionCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet); #endif - /// - /// Registers an encryption certificate retrieved from an embedded resource. - /// - /// The assembly containing the certificate. - /// The name of the embedded resource. - /// The password used to open the certificate. - /// An enumeration of flags indicating how and where to store the private key of the certificate. - /// The . - public OpenIddictValidationBuilder AddEncryptionCertificate( - Assembly assembly, string resource, - string? password, X509KeyStorageFlags flags) + /// + /// Registers an encryption certificate retrieved from an embedded resource. + /// + /// The assembly containing the certificate. + /// The name of the embedded resource. + /// The password used to open the certificate. + /// An enumeration of flags indicating how and where to store the private key of the certificate. + /// The . + public OpenIddictValidationBuilder AddEncryptionCertificate( + Assembly assembly, string resource, + string? password, X509KeyStorageFlags flags) + { + if (assembly is null) { - if (assembly is null) - { - throw new ArgumentNullException(nameof(assembly)); - } - - if (string.IsNullOrEmpty(resource)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); - } + throw new ArgumentNullException(nameof(assembly)); + } - using var stream = assembly.GetManifestResourceStream(resource); - if (stream is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0064)); - } + if (string.IsNullOrEmpty(resource)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0062), nameof(resource)); + } - return AddEncryptionCertificate(stream, password, flags); + using var stream = assembly.GetManifestResourceStream(resource); + if (stream is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0064)); } - /// - /// Registers an encryption certificate extracted from a stream. - /// - /// The stream containing the certificate. - /// The password used to open the certificate. - /// The . - public OpenIddictValidationBuilder AddEncryptionCertificate(Stream stream, string? password) + return AddEncryptionCertificate(stream, password, flags); + } + + /// + /// Registers an encryption certificate extracted from a stream. + /// + /// The stream containing the certificate. + /// The password used to open the certificate. + /// The . + public OpenIddictValidationBuilder AddEncryptionCertificate(Stream stream, string? password) #if SUPPORTS_EPHEMERAL_KEY_SETS - // Note: ephemeral key sets are currently not supported on macOS. - => AddEncryptionCertificate(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? - X509KeyStorageFlags.MachineKeySet : - X509KeyStorageFlags.EphemeralKeySet); + // Note: ephemeral key sets are currently not supported on macOS. + => AddEncryptionCertificate(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + X509KeyStorageFlags.MachineKeySet : + X509KeyStorageFlags.EphemeralKeySet); #else - => AddEncryptionCertificate(stream, password, X509KeyStorageFlags.MachineKeySet); + => AddEncryptionCertificate(stream, password, X509KeyStorageFlags.MachineKeySet); #endif - /// - /// Registers an encryption certificate extracted from a stream. - /// - /// The stream containing the certificate. - /// The password used to open the certificate. - /// - /// An enumeration of flags indicating how and where - /// to store the private key of the certificate. - /// - /// The . - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The X.509 certificate is attached to the server options.")] - public OpenIddictValidationBuilder AddEncryptionCertificate( - Stream stream, string? password, X509KeyStorageFlags flags) + /// + /// Registers an encryption certificate extracted from a stream. + /// + /// The stream containing the certificate. + /// The password used to open the certificate. + /// + /// An enumeration of flags indicating how and where + /// to store the private key of the certificate. + /// + /// The . + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The X.509 certificate is attached to the server options.")] + public OpenIddictValidationBuilder AddEncryptionCertificate( + Stream stream, string? password, X509KeyStorageFlags flags) + { + if (stream is null) { - if (stream is null) - { - throw new ArgumentNullException(nameof(stream)); - } - - using var buffer = new MemoryStream(); - stream.CopyTo(buffer); - - return AddEncryptionCertificate(new X509Certificate2(buffer.ToArray(), password, flags)); + throw new ArgumentNullException(nameof(stream)); } - /// - /// Registers an encryption certificate retrieved from the X.509 user or machine store. - /// - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The . - public OpenIddictValidationBuilder AddEncryptionCertificate(string thumbprint) - { - if (string.IsNullOrEmpty(thumbprint)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); - } - - var certificate = GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint); - if (certificate is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); - } - - return AddEncryptionCertificate(certificate); + using var buffer = new MemoryStream(); + stream.CopyTo(buffer); - static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint) - { - using var store = new X509Store(StoreName.My, location); - store.Open(OpenFlags.ReadOnly); + return AddEncryptionCertificate(new X509Certificate2(buffer.ToArray(), password, flags)); + } - return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) - .OfType() - .SingleOrDefault(); - } + /// + /// Registers an encryption certificate retrieved from the X.509 user or machine store. + /// + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The . + public OpenIddictValidationBuilder AddEncryptionCertificate(string thumbprint) + { + if (string.IsNullOrEmpty(thumbprint)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); } - /// - /// Registers an encryption certificate retrieved from the specified X.509 store. - /// - /// The thumbprint of the certificate used to identify it in the X.509 store. - /// The name of the X.509 store. - /// The location of the X.509 store. - /// The . - public OpenIddictValidationBuilder AddEncryptionCertificate( - string thumbprint, StoreName name, StoreLocation location) + var certificate = GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint); + if (certificate is null) { - if (string.IsNullOrEmpty(thumbprint)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); + } + + return AddEncryptionCertificate(certificate); - using var store = new X509Store(name, location); + static X509Certificate2? GetCertificate(StoreLocation location, string thumbprint) + { + using var store = new X509Store(StoreName.My, location); store.Open(OpenFlags.ReadOnly); - var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) + return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) .OfType() .SingleOrDefault(); - - if (certificate is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); - } - - return AddEncryptionCertificate(certificate); } + } - /// - /// Registers the specified values as valid audiences. Setting the audiences is recommended - /// when the authorization server issues access tokens for multiple distinct resource servers. - /// - /// The audiences valid for this resource server. - /// The . - public OpenIddictValidationBuilder AddAudiences(params string[] audiences) + /// + /// Registers an encryption certificate retrieved from the specified X.509 store. + /// + /// The thumbprint of the certificate used to identify it in the X.509 store. + /// The name of the X.509 store. + /// The location of the X.509 store. + /// The . + public OpenIddictValidationBuilder AddEncryptionCertificate( + string thumbprint, StoreName name, StoreLocation location) + { + if (string.IsNullOrEmpty(thumbprint)) { - if (audiences is null) - { - throw new ArgumentNullException(nameof(audiences)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0065), nameof(thumbprint)); + } - if (audiences.Any(audience => string.IsNullOrEmpty(audience))) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0123), nameof(audiences)); - } + using var store = new X509Store(name, location); + store.Open(OpenFlags.ReadOnly); - return Configure(options => options.Audiences.UnionWith(audiences)); - } + var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) + .OfType() + .SingleOrDefault(); - /// - /// Enables authorization validation so that a database call is made for each API request - /// to ensure the authorization associated with the access token is still valid. - /// Note: enabling this option may have an impact on performance and - /// can only be used with an OpenIddict-based authorization server. - /// - /// The . - public OpenIddictValidationBuilder EnableAuthorizationEntryValidation() - => Configure(options => options.EnableAuthorizationEntryValidation = true); - - /// - /// Enables token validation so that a database call is made for each API request - /// to ensure the token entry associated with the access token is still valid. - /// Note: enabling this option may have an impact on performance but is required - /// when the OpenIddict server is configured to use reference tokens. - /// - /// The . - public OpenIddictValidationBuilder EnableTokenEntryValidation() - => Configure(options => options.EnableTokenEntryValidation = true); - - /// - /// Sets a static OpenID Connect server configuration, that will be used to - /// resolve the metadata/introspection endpoints and the issuer signing keys. - /// - /// The server configuration. - /// The . - public OpenIddictValidationBuilder SetConfiguration(OpenIdConnectConfiguration configuration) + if (certificate is null) { - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } - - return Configure(options => options.Configuration = configuration); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0066)); } - /// - /// Sets the client identifier client_id used when communicating - /// with the remote authorization server (e.g for introspection). - /// - /// The client identifier. - /// The . - public OpenIddictValidationBuilder SetClientId(string identifier) - { - if (string.IsNullOrEmpty(identifier)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(identifier)); - } + return AddEncryptionCertificate(certificate); + } - return Configure(options => options.ClientId = identifier); + /// + /// Registers the specified values as valid audiences. Setting the audiences is recommended + /// when the authorization server issues access tokens for multiple distinct resource servers. + /// + /// The audiences valid for this resource server. + /// The . + public OpenIddictValidationBuilder AddAudiences(params string[] audiences) + { + if (audiences is null) + { + throw new ArgumentNullException(nameof(audiences)); } - /// - /// Sets the client identifier client_secret used when communicating - /// with the remote authorization server (e.g for introspection). - /// - /// The client secret. - /// The . - public OpenIddictValidationBuilder SetClientSecret(string secret) + if (audiences.Any(audience => string.IsNullOrEmpty(audience))) { - if (string.IsNullOrEmpty(secret)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0125), nameof(secret)); - } - - return Configure(options => options.ClientSecret = secret); + throw new ArgumentException(SR.GetResourceString(SR.ID0123), nameof(audiences)); } - /// - /// Sets the issuer address, which is used to determine the actual location of the - /// OAuth 2.0/OpenID Connect configuration document when using provider discovery. - /// - /// The issuer address. - /// The . - public OpenIddictValidationBuilder SetIssuer(Uri address) + return Configure(options => options.Audiences.UnionWith(audiences)); + } + + /// + /// Enables authorization validation so that a database call is made for each API request + /// to ensure the authorization associated with the access token is still valid. + /// Note: enabling this option may have an impact on performance and + /// can only be used with an OpenIddict-based authorization server. + /// + /// The . + public OpenIddictValidationBuilder EnableAuthorizationEntryValidation() + => Configure(options => options.EnableAuthorizationEntryValidation = true); + + /// + /// Enables token validation so that a database call is made for each API request + /// to ensure the token entry associated with the access token is still valid. + /// Note: enabling this option may have an impact on performance but is required + /// when the OpenIddict server is configured to use reference tokens. + /// + /// The . + public OpenIddictValidationBuilder EnableTokenEntryValidation() + => Configure(options => options.EnableTokenEntryValidation = true); + + /// + /// Sets a static OpenID Connect server configuration, that will be used to + /// resolve the metadata/introspection endpoints and the issuer signing keys. + /// + /// The server configuration. + /// The . + public OpenIddictValidationBuilder SetConfiguration(OpenIdConnectConfiguration configuration) + { + if (configuration is null) { - if (address is null) - { - throw new ArgumentNullException(nameof(address)); - } + throw new ArgumentNullException(nameof(configuration)); + } + + return Configure(options => options.Configuration = configuration); + } - return Configure(options => options.Issuer = address); + /// + /// Sets the client identifier client_id used when communicating + /// with the remote authorization server (e.g for introspection). + /// + /// The client identifier. + /// The . + public OpenIddictValidationBuilder SetClientId(string identifier) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(identifier)); } - /// - /// Sets the issuer address, which is used to determine the actual location of the - /// OAuth 2.0/OpenID Connect configuration document when using provider discovery. - /// - /// The issuer address. - /// The . - public OpenIddictValidationBuilder SetIssuer(string address) + return Configure(options => options.ClientId = identifier); + } + + /// + /// Sets the client identifier client_secret used when communicating + /// with the remote authorization server (e.g for introspection). + /// + /// The client secret. + /// The . + public OpenIddictValidationBuilder SetClientSecret(string secret) + { + if (string.IsNullOrEmpty(secret)) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0126), nameof(address)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0125), nameof(secret)); + } - if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0127), nameof(address)); - } + return Configure(options => options.ClientSecret = secret); + } - return SetIssuer(uri); + /// + /// Sets the issuer address, which is used to determine the actual location of the + /// OAuth 2.0/OpenID Connect configuration document when using provider discovery. + /// + /// The issuer address. + /// The . + public OpenIddictValidationBuilder SetIssuer(Uri address) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); } - /// - /// Configures OpenIddict to use introspection instead of local/direct validation. - /// - /// The . - public OpenIddictValidationBuilder UseIntrospection() - => Configure(options => options.ValidationType = OpenIddictValidationType.Introspection); + return Configure(options => options.Issuer = address); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object? obj) => base.Equals(obj); + /// + /// Sets the issuer address, which is used to determine the actual location of the + /// OAuth 2.0/OpenID Connect configuration document when using provider discovery. + /// + /// The issuer address. + /// The . + public OpenIddictValidationBuilder SetIssuer(string address) + { + if (string.IsNullOrEmpty(address)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0126), nameof(address)); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => base.GetHashCode(); + if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0127), nameof(address)); + } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public override string? ToString() => base.ToString(); + return SetIssuer(uri); } + + /// + /// Configures OpenIddict to use introspection instead of local/direct validation. + /// + /// The . + public OpenIddictValidationBuilder UseIntrospection() + => Configure(options => options.ValidationType = OpenIddictValidationType.Introspection); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() => base.ToString(); } diff --git a/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs b/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs index ebbfef34..806de529 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs @@ -13,146 +13,145 @@ using Microsoft.IdentityModel.Tokens; using static OpenIddict.Validation.OpenIddictValidationEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Contains the methods required to ensure that the OpenIddict validation configuration is valid. +/// +public class OpenIddictValidationConfiguration : IPostConfigureOptions { + private readonly OpenIddictValidationService _service; + + public OpenIddictValidationConfiguration(OpenIddictValidationService service) + => _service = service; + /// - /// Contains the methods required to ensure that the OpenIddict validation configuration is valid. + /// Populates the default OpenIddict validation options and ensures + /// that the configuration is in a consistent and valid state. /// - public class OpenIddictValidationConfiguration : IPostConfigureOptions + /// The name of the options instance to configure, if applicable. + /// The options instance to initialize. + public void PostConfigure(string name, OpenIddictValidationOptions options) { - private readonly OpenIddictValidationService _service; - - public OpenIddictValidationConfiguration(OpenIddictValidationService service) - => _service = service; - - /// - /// Populates the default OpenIddict validation options and ensures - /// that the configuration is in a consistent and valid state. - /// - /// The name of the options instance to configure, if applicable. - /// The options instance to initialize. - public void PostConfigure(string name, OpenIddictValidationOptions options) + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (options.JsonWebTokenHandler is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0075)); + } + + if (options.Configuration is null && options.ConfigurationManager is null && + options.Issuer is null && options.MetadataAddress is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0128)); + } + + if (options.ValidationType == OpenIddictValidationType.Introspection) { - if (options is null) + if (!options.Handlers.Any(descriptor => descriptor.ContextType == typeof(ApplyIntrospectionRequestContext))) { - throw new ArgumentNullException(nameof(options)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0129)); } - if (options.JsonWebTokenHandler is null) + if (options.Issuer is null && options.MetadataAddress is null) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0075)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0130)); } - if (options.Configuration is null && options.ConfigurationManager is null && - options.Issuer is null && options.MetadataAddress is null) + if (string.IsNullOrEmpty(options.ClientId)) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0128)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0131)); } - if (options.ValidationType == OpenIddictValidationType.Introspection) + if (string.IsNullOrEmpty(options.ClientSecret)) { - if (!options.Handlers.Any(descriptor => descriptor.ContextType == typeof(ApplyIntrospectionRequestContext))) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0129)); - } - - if (options.Issuer is null && options.MetadataAddress is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0130)); - } - - if (string.IsNullOrEmpty(options.ClientId)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0131)); - } - - if (string.IsNullOrEmpty(options.ClientSecret)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0132)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0132)); + } - if (options.EnableAuthorizationEntryValidation) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0133)); - } + if (options.EnableAuthorizationEntryValidation) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0133)); + } - if (options.EnableTokenEntryValidation) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0134)); - } + if (options.EnableTokenEntryValidation) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0134)); } + } + + // If all the registered encryption credentials are backed by a X.509 certificate, at least one of them must be valid. + if (options.EncryptionCredentials.Count != 0 && + options.EncryptionCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey && + (x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now))) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0087)); + } - // If all the registered encryption credentials are backed by a X.509 certificate, at least one of them must be valid. - if (options.EncryptionCredentials.Count != 0 && - options.EncryptionCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey && - (x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now))) + if (options.ConfigurationManager is null) + { + if (options.Configuration is not null) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0087)); + options.ConfigurationManager = new StaticConfigurationManager(options.Configuration); } - if (options.ConfigurationManager is null) + else { - if (options.Configuration is not null) + if (!options.Handlers.Any(descriptor => descriptor.ContextType == typeof(ApplyConfigurationRequestContext)) || + !options.Handlers.Any(descriptor => descriptor.ContextType == typeof(ApplyCryptographyRequestContext))) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0135)); + } + + if (options.MetadataAddress is null) { - options.ConfigurationManager = new StaticConfigurationManager(options.Configuration); + options.MetadataAddress = new Uri(".well-known/openid-configuration", UriKind.Relative); } - else + if (!options.MetadataAddress.IsAbsoluteUri) { - if (!options.Handlers.Any(descriptor => descriptor.ContextType == typeof(ApplyConfigurationRequestContext)) || - !options.Handlers.Any(descriptor => descriptor.ContextType == typeof(ApplyCryptographyRequestContext))) + var issuer = options.Issuer; + if (issuer is null || !issuer.IsAbsoluteUri) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0135)); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0136)); } - if (options.MetadataAddress is null) + if (!string.IsNullOrEmpty(issuer.Fragment) || !string.IsNullOrEmpty(issuer.Query)) { - options.MetadataAddress = new Uri(".well-known/openid-configuration", UriKind.Relative); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0137)); } - if (!options.MetadataAddress.IsAbsoluteUri) + if (!issuer.OriginalString.EndsWith("/", StringComparison.Ordinal)) { - var issuer = options.Issuer; - if (issuer is null || !issuer.IsAbsoluteUri) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0136)); - } - - if (!string.IsNullOrEmpty(issuer.Fragment) || !string.IsNullOrEmpty(issuer.Query)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0137)); - } - - if (!issuer.OriginalString.EndsWith("/", StringComparison.Ordinal)) - { - issuer = new Uri(issuer.OriginalString + "/", UriKind.Absolute); - } - - if (options.MetadataAddress.OriginalString.StartsWith("/", StringComparison.Ordinal)) - { - options.MetadataAddress = new Uri(options.MetadataAddress.OriginalString.Substring( - 1, options.MetadataAddress.OriginalString.Length - 1), UriKind.Relative); - } - - options.MetadataAddress = new Uri(issuer, options.MetadataAddress); + issuer = new Uri(issuer.OriginalString + "/", UriKind.Absolute); } - options.ConfigurationManager = new ConfigurationManager( - options.MetadataAddress.AbsoluteUri, new OpenIddictValidationRetriever(_service)) + if (options.MetadataAddress.OriginalString.StartsWith("/", StringComparison.Ordinal)) { - AutomaticRefreshInterval = ConfigurationManager.DefaultAutomaticRefreshInterval, - RefreshInterval = ConfigurationManager.DefaultRefreshInterval - }; + options.MetadataAddress = new Uri(options.MetadataAddress.OriginalString.Substring( + 1, options.MetadataAddress.OriginalString.Length - 1), UriKind.Relative); + } + + options.MetadataAddress = new Uri(issuer, options.MetadataAddress); } + + options.ConfigurationManager = new ConfigurationManager( + options.MetadataAddress.AbsoluteUri, new OpenIddictValidationRetriever(_service)) + { + AutomaticRefreshInterval = ConfigurationManager.DefaultAutomaticRefreshInterval, + RefreshInterval = ConfigurationManager.DefaultRefreshInterval + }; } + } - // Sort the handlers collection using the order associated with each handler. - options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order)); + // Sort the handlers collection using the order associated with each handler. + options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order)); - // Attach the encryption credentials to the token validation parameters. - options.TokenValidationParameters.TokenDecryptionKeys = - from credentials in options.EncryptionCredentials - select credentials.Key; - } + // Attach the encryption credentials to the token validation parameters. + options.TokenValidationParameters.TokenDecryptionKeys = + from credentials in options.EncryptionCredentials + select credentials.Key; } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs b/src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs index db95f369..cbc3c242 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationDispatcher.cs @@ -12,127 +12,126 @@ using Microsoft.Extensions.Options; using static OpenIddict.Validation.OpenIddictValidationEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public class OpenIddictValidationDispatcher : IOpenIddictValidationDispatcher { - public class OpenIddictValidationDispatcher : IOpenIddictValidationDispatcher + private readonly ILogger _logger; + private readonly IOptionsMonitor _options; + private readonly IServiceProvider _provider; + + /// + /// Creates a new instance of the class. + /// + public OpenIddictValidationDispatcher( + ILogger logger, + IOptionsMonitor options, + IServiceProvider provider) + { + _logger = logger; + _options = options; + _provider = provider; + } + + public async ValueTask DispatchAsync(TContext context) where TContext : BaseContext { - private readonly ILogger _logger; - private readonly IOptionsMonitor _options; - private readonly IServiceProvider _provider; - - /// - /// Creates a new instance of the class. - /// - public OpenIddictValidationDispatcher( - ILogger logger, - IOptionsMonitor options, - IServiceProvider provider) + if (context is null) { - _logger = logger; - _options = options; - _provider = provider; + throw new ArgumentNullException(nameof(context)); } - public async ValueTask DispatchAsync(TContext context) where TContext : BaseContext + await foreach (var handler in GetHandlersAsync()) { - if (context is null) + try { - throw new ArgumentNullException(nameof(context)); + await handler.HandleAsync(context); } - await foreach (var handler in GetHandlersAsync()) + catch (Exception exception) when (_logger.IsEnabled(LogLevel.Debug)) { - try - { - await handler.HandleAsync(context); - } + _logger.LogDebug(exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); - catch (Exception exception) when (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(exception, SR.GetResourceString(SR.ID6132), handler.GetType().FullName, typeof(TContext).FullName); - - throw; - } - - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName); - } - - switch (context) - { - case BaseRequestContext { IsRequestHandled: true }: - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName); - } - return; - - case BaseRequestContext { IsRequestSkipped: true }: - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName); - } - return; - - case BaseValidatingContext { IsRejected: true }: - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug(SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName); - } - return; - - default: continue; - } + throw; } - async IAsyncEnumerable> GetHandlersAsync() + if (_logger.IsEnabled(LogLevel.Debug)) { - // Note: the descriptors collection is sorted during options initialization for performance reasons. - var descriptors = _options.CurrentValue.Handlers; - if (descriptors.Count == 0) - { - yield break; - } + _logger.LogDebug(SR.GetResourceString(SR.ID6133), typeof(TContext).FullName, handler.GetType().FullName); + } - for (var index = 0; index < descriptors.Count; index++) - { - var descriptor = descriptors[index]; - if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor)) + switch (context) + { + case BaseRequestContext { IsRequestHandled: true }: + if (_logger.IsEnabled(LogLevel.Debug)) { - continue; + _logger.LogDebug(SR.GetResourceString(SR.ID6134), typeof(TContext).FullName, handler.GetType().FullName); } + return; - var handler = descriptor.ServiceDescriptor.ImplementationInstance is not null ? - descriptor.ServiceDescriptor.ImplementationInstance as IOpenIddictValidationHandler : - _provider.GetService(descriptor.ServiceDescriptor.ServiceType) as IOpenIddictValidationHandler; + case BaseRequestContext { IsRequestSkipped: true }: + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug(SR.GetResourceString(SR.ID6135), typeof(TContext).FullName, handler.GetType().FullName); + } + return; - if (handler is null) + case BaseValidatingContext { IsRejected: true }: + if (_logger.IsEnabled(LogLevel.Debug)) { - throw new InvalidOperationException(SR.FormatID0138(descriptor.ServiceDescriptor.ServiceType)); + _logger.LogDebug(SR.GetResourceString(SR.ID6136), typeof(TContext).FullName, handler.GetType().FullName); } + return; - yield return handler; - } + default: continue; + } + } + + async IAsyncEnumerable> GetHandlersAsync() + { + // Note: the descriptors collection is sorted during options initialization for performance reasons. + var descriptors = _options.CurrentValue.Handlers; + if (descriptors.Count == 0) + { + yield break; } - async ValueTask IsActiveAsync(OpenIddictValidationHandlerDescriptor descriptor) + for (var index = 0; index < descriptors.Count; index++) { - for (var index = 0; index < descriptor.FilterTypes.Length; index++) + var descriptor = descriptors[index]; + if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor)) { - if (!(_provider.GetService(descriptor.FilterTypes[index]) is IOpenIddictValidationHandlerFilter filter)) - { - throw new InvalidOperationException(SR.FormatID0099(descriptor.FilterTypes[index])); - } + continue; + } - if (!await filter.IsActiveAsync(context)) - { - return false; - } + var handler = descriptor.ServiceDescriptor.ImplementationInstance is not null ? + descriptor.ServiceDescriptor.ImplementationInstance as IOpenIddictValidationHandler : + _provider.GetService(descriptor.ServiceDescriptor.ServiceType) as IOpenIddictValidationHandler; + + if (handler is null) + { + throw new InvalidOperationException(SR.FormatID0138(descriptor.ServiceDescriptor.ServiceType)); } - return true; + yield return handler; } } + + async ValueTask IsActiveAsync(OpenIddictValidationHandlerDescriptor descriptor) + { + for (var index = 0; index < descriptor.FilterTypes.Length; index++) + { + if (!(_provider.GetService(descriptor.FilterTypes[index]) is IOpenIddictValidationHandlerFilter filter)) + { + throw new InvalidOperationException(SR.FormatID0099(descriptor.FilterTypes[index])); + } + + if (!await filter.IsActiveAsync(context)) + { + return false; + } + } + + return true; + } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationEndpointType.cs b/src/OpenIddict.Validation/OpenIddictValidationEndpointType.cs index d85f32e6..33579996 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationEndpointType.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationEndpointType.cs @@ -4,16 +4,15 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Represents the type of an OpenIddict validation endpoint. +/// +public enum OpenIddictValidationEndpointType { /// - /// Represents the type of an OpenIddict validation endpoint. + /// Unknown endpoint. /// - public enum OpenIddictValidationEndpointType - { - /// - /// Unknown endpoint. - /// - Unknown = 0 - } + Unknown = 0 } diff --git a/src/OpenIddict.Validation/OpenIddictValidationEvents.Discovery.cs b/src/OpenIddict.Validation/OpenIddictValidationEvents.Discovery.cs index bd453e6b..771929c7 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationEvents.Discovery.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationEvents.Discovery.cs @@ -8,244 +8,243 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public static partial class OpenIddictValidationEvents { - public static partial class OpenIddictValidationEvents + /// + /// Represents an event called for each request to the configuration endpoint + /// to give the user code a chance to add parameters to the configuration request. + /// + public class PrepareConfigurationRequestContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public PrepareConfigurationRequestContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + } + + /// + /// Represents an event called for each request to the configuration endpoint + /// to send the configuration request to the remote authorization server. + /// + public class ApplyConfigurationRequestContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyConfigurationRequestContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + } + + /// + /// Represents an event called for each configuration response + /// to extract the response parameters from the server response. + /// + public class ExtractConfigurationResponseContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public ExtractConfigurationResponseContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response, or null if it wasn't extracted yet. + /// + public OpenIddictResponse? Response + { + get => Transaction.Response; + set => Transaction.Response = value; + } + } + + /// + /// Represents an event called for each configuration response. + /// + public class HandleConfigurationResponseContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public HandleConfigurationResponseContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the OpenID Connect configuration. + /// + public OpenIdConnectConfiguration Configuration { get; } = new OpenIdConnectConfiguration(); + } + + /// + /// Represents an event called for each request to the cryptography endpoint + /// to give the user code a chance to add parameters to the cryptography request. + /// + public class PrepareCryptographyRequestContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public PrepareCryptographyRequestContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + } + + /// + /// Represents an event called for each request to the cryptography endpoint + /// to send the cryptography request to the remote authorization server. + /// + public class ApplyCryptographyRequestContext : BaseExternalContext { /// - /// Represents an event called for each request to the configuration endpoint - /// to give the user code a chance to add parameters to the configuration request. - /// - public class PrepareConfigurationRequestContext : BaseExternalContext - { - /// - /// Creates a new instance of the class. - /// - public PrepareConfigurationRequestContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - } - - /// - /// Represents an event called for each request to the configuration endpoint - /// to send the configuration request to the remote authorization server. - /// - public class ApplyConfigurationRequestContext : BaseExternalContext - { - /// - /// Creates a new instance of the class. - /// - public ApplyConfigurationRequestContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - } - - /// - /// Represents an event called for each configuration response - /// to extract the response parameters from the server response. - /// - public class ExtractConfigurationResponseContext : BaseExternalContext - { - /// - /// Creates a new instance of the class. - /// - public ExtractConfigurationResponseContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response, or null if it wasn't extracted yet. - /// - public OpenIddictResponse? Response - { - get => Transaction.Response; - set => Transaction.Response = value; - } - } - - /// - /// Represents an event called for each configuration response. - /// - public class HandleConfigurationResponseContext : BaseExternalContext - { - /// - /// Creates a new instance of the class. - /// - public HandleConfigurationResponseContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the OpenID Connect configuration. - /// - public OpenIdConnectConfiguration Configuration { get; } = new OpenIdConnectConfiguration(); - } - - /// - /// Represents an event called for each request to the cryptography endpoint - /// to give the user code a chance to add parameters to the cryptography request. - /// - public class PrepareCryptographyRequestContext : BaseExternalContext - { - /// - /// Creates a new instance of the class. - /// - public PrepareCryptographyRequestContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - } - - /// - /// Represents an event called for each request to the cryptography endpoint - /// to send the cryptography request to the remote authorization server. - /// - public class ApplyCryptographyRequestContext : BaseExternalContext - { - /// - /// Creates a new instance of the class. - /// - public ApplyCryptographyRequestContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - } - - /// - /// Represents an event called for each cryptography response - /// to extract the response parameters from the server response. - /// - public class ExtractCryptographyResponseContext : BaseExternalContext - { - /// - /// Creates a new instance of the class. - /// - public ExtractCryptographyResponseContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response, or null if it wasn't extracted yet. - /// - public OpenIddictResponse? Response - { - get => Transaction.Response; - set => Transaction.Response = value; - } - } - - /// - /// Represents an event called for each cryptography response. - /// - public class HandleCryptographyResponseContext : BaseExternalContext - { - /// - /// Creates a new instance of the class. - /// - public HandleCryptographyResponseContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the security keys. - /// - public JsonWebKeySet SecurityKeys { get; } = new JsonWebKeySet(); + /// Creates a new instance of the class. + /// + public ApplyCryptographyRequestContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; } } + + /// + /// Represents an event called for each cryptography response + /// to extract the response parameters from the server response. + /// + public class ExtractCryptographyResponseContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public ExtractCryptographyResponseContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response, or null if it wasn't extracted yet. + /// + public OpenIddictResponse? Response + { + get => Transaction.Response; + set => Transaction.Response = value; + } + } + + /// + /// Represents an event called for each cryptography response. + /// + public class HandleCryptographyResponseContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public HandleCryptographyResponseContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the security keys. + /// + public JsonWebKeySet SecurityKeys { get; } = new JsonWebKeySet(); + } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationEvents.Introspection.cs b/src/OpenIddict.Validation/OpenIddictValidationEvents.Introspection.cs index cf678280..195e7920 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationEvents.Introspection.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationEvents.Introspection.cs @@ -7,141 +7,140 @@ using System.Security.Claims; using OpenIddict.Abstractions; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public static partial class OpenIddictValidationEvents { - public static partial class OpenIddictValidationEvents + /// + /// Represents an event called for each request to the introspection endpoint + /// to give the user code a chance to add parameters to the introspection request. + /// + public class PrepareIntrospectionRequestContext : BaseExternalContext { /// - /// Represents an event called for each request to the introspection endpoint - /// to give the user code a chance to add parameters to the introspection request. + /// Creates a new instance of the class. /// - public class PrepareIntrospectionRequestContext : BaseExternalContext + public PrepareIntrospectionRequestContext(OpenIddictValidationTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public PrepareIntrospectionRequestContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the token sent to the introspection endpoint. - /// - public string? Token { get; set; } - - /// - /// Gets or sets the token type sent to the introspection endpoint. - /// - public string? TokenTypeHint { get; set; } } /// - /// Represents an event called for each request to the introspection endpoint - /// to send the introspection request to the remote authorization server. + /// Gets or sets the request. /// - public class ApplyIntrospectionRequestContext : BaseExternalContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public ApplyIntrospectionRequestContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } + get => Transaction.Request!; + set => Transaction.Request = value; } /// - /// Represents an event called for each introspection response - /// to extract the response parameters from the server response. + /// Gets or sets the token sent to the introspection endpoint. + /// + public string? Token { get; set; } + + /// + /// Gets or sets the token type sent to the introspection endpoint. /// - public class ExtractIntrospectionResponseContext : BaseExternalContext + public string? TokenTypeHint { get; set; } + } + + /// + /// Represents an event called for each request to the introspection endpoint + /// to send the introspection request to the remote authorization server. + /// + public class ApplyIntrospectionRequestContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public ApplyIntrospectionRequestContext(OpenIddictValidationTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ExtractIntrospectionResponseContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response, or null if it wasn't extracted yet. - /// - public OpenIddictResponse? Response - { - get => Transaction.Response; - set => Transaction.Response = value; - } } /// - /// Represents an event called for each introspection response. + /// Gets or sets the request. /// - public class HandleIntrospectionResponseContext : BaseExternalContext + public OpenIddictRequest Request { - /// - /// Creates a new instance of the class. - /// - public HandleIntrospectionResponseContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets or sets the token sent to the introspection endpoint. - /// - public string? Token { get; set; } - - /// - /// Gets or sets the principal containing the claims resolved from the introspection response. - /// - public ClaimsPrincipal? Principal { get; set; } + get => Transaction.Request!; + set => Transaction.Request = value; } } + + /// + /// Represents an event called for each introspection response + /// to extract the response parameters from the server response. + /// + public class ExtractIntrospectionResponseContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public ExtractIntrospectionResponseContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response, or null if it wasn't extracted yet. + /// + public OpenIddictResponse? Response + { + get => Transaction.Response; + set => Transaction.Response = value; + } + } + + /// + /// Represents an event called for each introspection response. + /// + public class HandleIntrospectionResponseContext : BaseExternalContext + { + /// + /// Creates a new instance of the class. + /// + public HandleIntrospectionResponseContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets or sets the token sent to the introspection endpoint. + /// + public string? Token { get; set; } + + /// + /// Gets or sets the principal containing the claims resolved from the introspection response. + /// + public ClaimsPrincipal? Principal { get; set; } + } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs b/src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs index a0b8afd0..4067b03e 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs @@ -11,62 +11,61 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public static partial class OpenIddictValidationEvents { - public static partial class OpenIddictValidationEvents + /// + /// Represents an event called when validating a token. + /// + public class ValidateTokenContext : BaseValidatingContext { /// - /// Represents an event called when validating a token. + /// Creates a new instance of the class. /// - public class ValidateTokenContext : BaseValidatingContext + public ValidateTokenContext(OpenIddictValidationTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ValidateTokenContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } + } - /// - /// Gets or sets the request, or null if it is not available. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } + /// + /// Gets or sets the request, or null if it is not available. + /// + public OpenIddictRequest? Request + { + get => Transaction.Request; + set => Transaction.Request = value; + } - /// - /// Gets or sets the security token handler used to validate the token. - /// - public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!; + /// + /// Gets or sets the security token handler used to validate the token. + /// + public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!; - /// - /// Gets or sets the validation parameters used to verify the authenticity of tokens. - /// - public TokenValidationParameters TokenValidationParameters { get; set; } = default!; + /// + /// Gets or sets the validation parameters used to verify the authenticity of tokens. + /// + public TokenValidationParameters TokenValidationParameters { get; set; } = default!; - /// - /// Gets or sets the token to validate. - /// - public string Token { get; set; } = default!; + /// + /// Gets or sets the token to validate. + /// + public string Token { get; set; } = default!; - /// - /// Gets or sets the token entry identifier associated with the token, if applicable. - /// - public string? TokenId { get; set; } + /// + /// Gets or sets the token entry identifier associated with the token, if applicable. + /// + public string? TokenId { get; set; } - /// - /// Gets or sets the security principal resolved from the token. - /// - public ClaimsPrincipal? Principal { get; set; } + /// + /// Gets or sets the security principal resolved from the token. + /// + public ClaimsPrincipal? Principal { get; set; } - /// - /// Gets the token types that are considered valid. If no value is - /// explicitly specified, all supported tokens are considered valid. - /// - public HashSet ValidTokenTypes { get; } = new(StringComparer.OrdinalIgnoreCase); - } + /// + /// Gets the token types that are considered valid. If no value is + /// explicitly specified, all supported tokens are considered valid. + /// + public HashSet ValidTokenTypes { get; } = new(StringComparer.OrdinalIgnoreCase); } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationEvents.cs b/src/OpenIddict.Validation/OpenIddictValidationEvents.cs index 24719498..255e70ea 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationEvents.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationEvents.cs @@ -11,315 +11,314 @@ using System.Security.Claims; using Microsoft.Extensions.Logging; using OpenIddict.Abstractions; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public static partial class OpenIddictValidationEvents { - public static partial class OpenIddictValidationEvents + /// + /// Represents an abstract base class used for certain event contexts. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class BaseContext { /// - /// Represents an abstract base class used for certain event contexts. + /// Creates a new instance of the class. + /// + protected BaseContext(OpenIddictValidationTransaction transaction) + => Transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); + + /// + /// Gets the environment associated with the current request being processed. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class BaseContext + public OpenIddictValidationTransaction Transaction { get; } + + /// + /// Gets or sets the endpoint type that handled the request, if applicable. + /// + public OpenIddictValidationEndpointType EndpointType { - /// - /// Creates a new instance of the class. - /// - protected BaseContext(OpenIddictValidationTransaction transaction) - => Transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); - - /// - /// Gets the environment associated with the current request being processed. - /// - public OpenIddictValidationTransaction Transaction { get; } - - /// - /// Gets or sets the endpoint type that handled the request, if applicable. - /// - public OpenIddictValidationEndpointType EndpointType - { - get => Transaction.EndpointType; - set => Transaction.EndpointType = value; - } - - /// - /// Gets or sets the issuer address associated with the current transaction, if available. - /// - public Uri? Issuer - { - get => Transaction.Issuer; - set => Transaction.Issuer = value; - } - - /// - /// Gets the logger responsible of logging processed operations. - /// - public ILogger Logger => Transaction.Logger; - - /// - /// Gets the OpenIddict validation options. - /// - public OpenIddictValidationOptions Options => Transaction.Options; + get => Transaction.EndpointType; + set => Transaction.EndpointType = value; } /// - /// Represents an abstract base class used for certain event contexts. + /// Gets or sets the issuer address associated with the current transaction, if available. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class BaseRequestContext : BaseContext + public Uri? Issuer { - /// - /// Creates a new instance of the class. - /// - protected BaseRequestContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets a boolean indicating whether the request was fully handled. - /// - public bool IsRequestHandled { get; private set; } - - /// - /// Gets a boolean indicating whether the request processing was skipped. - /// - public bool IsRequestSkipped { get; private set; } - - /// - /// Marks the request as fully handled. Once declared handled, - /// a request shouldn't be processed further by the underlying host. - /// - public void HandleRequest() => IsRequestHandled = true; - - /// - /// Marks the request as skipped. Once declared skipped, a request - /// shouldn't be processed further by OpenIddict but should be allowed - /// to go through the next components in the processing pipeline - /// (if this pattern is supported by the underlying host). - /// - public void SkipRequest() => IsRequestSkipped = true; + get => Transaction.Issuer; + set => Transaction.Issuer = value; } /// - /// Represents an abstract base class used for certain event contexts. + /// Gets the logger responsible of logging processed operations. + /// + public ILogger Logger => Transaction.Logger; + + /// + /// Gets the OpenIddict validation options. + /// + public OpenIddictValidationOptions Options => Transaction.Options; + } + + /// + /// Represents an abstract base class used for certain event contexts. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class BaseRequestContext : BaseContext + { + /// + /// Creates a new instance of the class. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class BaseExternalContext : BaseValidatingContext + protected BaseRequestContext(OpenIddictValidationTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - protected BaseExternalContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the address of the external endpoint to communicate with. - /// - public Uri? Address { get; set; } } /// - /// Represents an abstract base class used for certain event contexts. + /// Gets a boolean indicating whether the request was fully handled. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public abstract class BaseValidatingContext : BaseRequestContext + public bool IsRequestHandled { get; private set; } + + /// + /// Gets a boolean indicating whether the request processing was skipped. + /// + public bool IsRequestSkipped { get; private set; } + + /// + /// Marks the request as fully handled. Once declared handled, + /// a request shouldn't be processed further by the underlying host. + /// + public void HandleRequest() => IsRequestHandled = true; + + /// + /// Marks the request as skipped. Once declared skipped, a request + /// shouldn't be processed further by OpenIddict but should be allowed + /// to go through the next components in the processing pipeline + /// (if this pattern is supported by the underlying host). + /// + public void SkipRequest() => IsRequestSkipped = true; + } + + /// + /// Represents an abstract base class used for certain event contexts. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class BaseExternalContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + protected BaseExternalContext(OpenIddictValidationTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - protected BaseValidatingContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets a boolean indicating whether the request will be rejected. - /// - public bool IsRejected { get; protected set; } - - /// - /// Gets or sets the "error" parameter returned to the client application. - /// - public string? Error { get; private set; } - - /// - /// Gets or sets the "error_description" parameter returned to the client application. - /// - public string? ErrorDescription { get; private set; } - - /// - /// Gets or sets the "error_uri" parameter returned to the client application. - /// - public string? ErrorUri { get; private set; } - - /// - /// Rejects the request. - /// - /// The "error" parameter returned to the client application. - /// The "error_description" parameter returned to the client application. - /// The "error_uri" parameter returned to the client application. - public virtual void Reject(string? error = null, string? description = null, string? uri = null) - { - Error = error; - ErrorDescription = description; - ErrorUri = uri; - - IsRejected = true; - } } /// - /// Represents an event called when processing an incoming request. + /// Gets or sets the address of the external endpoint to communicate with. /// - public class ProcessRequestContext : BaseValidatingContext + public Uri? Address { get; set; } + } + + /// + /// Represents an abstract base class used for certain event contexts. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public abstract class BaseValidatingContext : BaseRequestContext + { + /// + /// Creates a new instance of the class. + /// + protected BaseValidatingContext(OpenIddictValidationTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ProcessRequestContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } } /// - /// Represents an event called when processing an errored response. + /// Gets a boolean indicating whether the request will be rejected. /// - public class ProcessErrorContext : BaseRequestContext + public bool IsRejected { get; protected set; } + + /// + /// Gets or sets the "error" parameter returned to the client application. + /// + public string? Error { get; private set; } + + /// + /// Gets or sets the "error_description" parameter returned to the client application. + /// + public string? ErrorDescription { get; private set; } + + /// + /// Gets or sets the "error_uri" parameter returned to the client application. + /// + public string? ErrorUri { get; private set; } + + /// + /// Rejects the request. + /// + /// The "error" parameter returned to the client application. + /// The "error_description" parameter returned to the client application. + /// The "error_uri" parameter returned to the client application. + public virtual void Reject(string? error = null, string? description = null, string? uri = null) + { + Error = error; + ErrorDescription = description; + ErrorUri = uri; + + IsRejected = true; + } + } + + /// + /// Represents an event called when processing an incoming request. + /// + public class ProcessRequestContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ProcessRequestContext(OpenIddictValidationTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ProcessErrorContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request, or null if it couldn't be extracted. - /// - public OpenIddictRequest? Request - { - get => Transaction.Request; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets or sets the error returned to the caller. - /// - public string? Error { get; set; } - - /// - /// Gets or sets the error description returned to the caller. - /// - public string? ErrorDescription { get; set; } - - /// - /// Gets or sets the error URL returned to the caller. - /// - public string? ErrorUri { get; set; } - - /// - /// Gets the additional parameters returned to the caller. - /// - public Dictionary Parameters { get; } = new(StringComparer.Ordinal); } + } + /// + /// Represents an event called when processing an errored response. + /// + public class ProcessErrorContext : BaseRequestContext + { /// - /// Represents an event called when processing an authentication operation. + /// Creates a new instance of the class. /// - public class ProcessAuthenticationContext : BaseValidatingContext + public ProcessErrorContext(OpenIddictValidationTransaction transaction) + : base(transaction) { - /// - /// Creates a new instance of the class. - /// - public ProcessAuthenticationContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the principal extracted from the access token, if applicable. - /// - public ClaimsPrincipal? AccessTokenPrincipal { get; set; } - - /// - /// Gets or sets the access token to validate, if applicable. - /// - public string? AccessToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether an access token - /// must be resolved for the authentication to considered valid. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool RequireAccessToken { get; set; } - - /// - /// Gets or sets a boolean indicating whether an access token - /// should be extracted from the current context and validated. - /// Note: overriding the value of this property is generally not - /// recommended, except when dealing with non-standard clients. - /// - public bool ValidateAccessToken { get; set; } } /// - /// Represents an event called when processing a challenge response. + /// Gets or sets the request, or null if it couldn't be extracted. /// - public class ProcessChallengeContext : BaseValidatingContext + public OpenIddictRequest? Request { - /// - /// Creates a new instance of the class. - /// - public ProcessChallengeContext(OpenIddictValidationTransaction transaction) - : base(transaction) - { - } - - /// - /// Gets or sets the request. - /// - public OpenIddictRequest Request - { - get => Transaction.Request!; - set => Transaction.Request = value; - } - - /// - /// Gets or sets the response. - /// - public OpenIddictResponse Response - { - get => Transaction.Response!; - set => Transaction.Response = value; - } - - /// - /// Gets the additional parameters returned to caller. - /// - public Dictionary Parameters { get; } = new(StringComparer.Ordinal); + get => Transaction.Request; + set => Transaction.Request = value; } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets or sets the error returned to the caller. + /// + public string? Error { get; set; } + + /// + /// Gets or sets the error description returned to the caller. + /// + public string? ErrorDescription { get; set; } + + /// + /// Gets or sets the error URL returned to the caller. + /// + public string? ErrorUri { get; set; } + + /// + /// Gets the additional parameters returned to the caller. + /// + public Dictionary Parameters { get; } = new(StringComparer.Ordinal); + } + + /// + /// Represents an event called when processing an authentication operation. + /// + public class ProcessAuthenticationContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ProcessAuthenticationContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the principal extracted from the access token, if applicable. + /// + public ClaimsPrincipal? AccessTokenPrincipal { get; set; } + + /// + /// Gets or sets the access token to validate, if applicable. + /// + public string? AccessToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether an access token + /// must be resolved for the authentication to considered valid. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool RequireAccessToken { get; set; } + + /// + /// Gets or sets a boolean indicating whether an access token + /// should be extracted from the current context and validated. + /// Note: overriding the value of this property is generally not + /// recommended, except when dealing with non-standard clients. + /// + public bool ValidateAccessToken { get; set; } + } + + /// + /// Represents an event called when processing a challenge response. + /// + public class ProcessChallengeContext : BaseValidatingContext + { + /// + /// Creates a new instance of the class. + /// + public ProcessChallengeContext(OpenIddictValidationTransaction transaction) + : base(transaction) + { + } + + /// + /// Gets or sets the request. + /// + public OpenIddictRequest Request + { + get => Transaction.Request!; + set => Transaction.Request = value; + } + + /// + /// Gets or sets the response. + /// + public OpenIddictResponse Response + { + get => Transaction.Response!; + set => Transaction.Response = value; + } + + /// + /// Gets the additional parameters returned to caller. + /// + public Dictionary Parameters { get; } = new(StringComparer.Ordinal); } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs b/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs index 259a18f0..5b9be6bf 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs @@ -11,77 +11,76 @@ using OpenIddict.Validation; using static OpenIddict.Validation.OpenIddictValidationHandlerFilters; using static OpenIddict.Validation.OpenIddictValidationHandlers; -namespace Microsoft.Extensions.DependencyInjection -{ - using Microsoft.Extensions.Options; +namespace Microsoft.Extensions.DependencyInjection; + +using Microsoft.Extensions.Options; +/// +/// Exposes extensions allowing to register the OpenIddict validation services. +/// +public static class OpenIddictValidationExtensions +{ /// - /// Exposes extensions allowing to register the OpenIddict validation services. + /// Registers the OpenIddict token validation services in the DI container. /// - public static class OpenIddictValidationExtensions + /// The services builder used by OpenIddict to register new services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictValidationBuilder AddValidation(this OpenIddictBuilder builder) { - /// - /// Registers the OpenIddict token validation services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictValidationBuilder AddValidation(this OpenIddictBuilder builder) + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.AddLogging(); - builder.Services.AddOptions(); + builder.Services.AddLogging(); + builder.Services.AddOptions(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddScoped(); - builder.Services.TryAddScoped(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); - // Register the built-in validation event handlers used by the OpenIddict validation components. - // Note: the order used here is not important, as the actual order is set in the options. - builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); + // Register the built-in validation event handlers used by the OpenIddict validation components. + // Note: the order used here is not important, as the actual order is set in the options. + builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); - // Register the built-in filters used by the default OpenIddict validation event handlers. - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); + // Register the built-in filters used by the default OpenIddict validation event handlers. + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); - // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< - IPostConfigureOptions, OpenIddictValidationConfiguration>()); + // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< + IPostConfigureOptions, OpenIddictValidationConfiguration>()); - return new OpenIddictValidationBuilder(builder.Services); - } + return new OpenIddictValidationBuilder(builder.Services); + } - /// - /// Registers the OpenIddict token validation services in the DI container. - /// - /// The services builder used by OpenIddict to register new services. - /// The configuration delegate used to configure the validation services. - /// This extension can be safely called multiple times. - /// The . - public static OpenIddictBuilder AddValidation( - this OpenIddictBuilder builder, - Action configuration) + /// + /// Registers the OpenIddict token validation services in the DI container. + /// + /// The services builder used by OpenIddict to register new services. + /// The configuration delegate used to configure the validation services. + /// This extension can be safely called multiple times. + /// The . + public static OpenIddictBuilder AddValidation( + this OpenIddictBuilder builder, + Action configuration) + { + if (builder is null) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + throw new ArgumentNullException(nameof(builder)); + } - if (configuration is null) - { - throw new ArgumentNullException(nameof(configuration)); - } + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } - configuration(builder.AddValidation()); + configuration(builder.AddValidation()); - return builder; - } + return builder; } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationFactory.cs b/src/OpenIddict.Validation/OpenIddictValidationFactory.cs index f0d9b872..1131c615 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationFactory.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationFactory.cs @@ -8,30 +8,29 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public class OpenIddictValidationFactory : IOpenIddictValidationFactory { - public class OpenIddictValidationFactory : IOpenIddictValidationFactory + private readonly ILogger _logger; + private readonly IOptionsMonitor _options; + + /// + /// Creates a new instance of the class. + /// + public OpenIddictValidationFactory( + ILogger logger, + IOptionsMonitor options) { - private readonly ILogger _logger; - private readonly IOptionsMonitor _options; + _logger = logger; + _options = options; + } - /// - /// Creates a new instance of the class. - /// - public OpenIddictValidationFactory( - ILogger logger, - IOptionsMonitor options) + public ValueTask CreateTransactionAsync() + => new ValueTask(new OpenIddictValidationTransaction { - _logger = logger; - _options = options; - } - - public ValueTask CreateTransactionAsync() - => new ValueTask(new OpenIddictValidationTransaction - { - Issuer = _options.CurrentValue.Issuer, - Logger = _logger, - Options = _options.CurrentValue - }); - } + Issuer = _options.CurrentValue.Issuer, + Logger = _logger, + Options = _options.CurrentValue + }); } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandler.cs b/src/OpenIddict.Validation/OpenIddictValidationHandler.cs index de7ad89f..54d5087c 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandler.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandler.cs @@ -8,31 +8,30 @@ using System; using System.Threading.Tasks; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Represents a handler able to process events. +/// +/// The type of the events handled by this instance. +public class OpenIddictValidationHandler : IOpenIddictValidationHandler where TContext : BaseContext { + private readonly Func _handler; + /// - /// Represents a handler able to process events. + /// Creates a new event using the specified handler delegate. /// - /// The type of the events handled by this instance. - public class OpenIddictValidationHandler : IOpenIddictValidationHandler where TContext : BaseContext - { - private readonly Func _handler; - - /// - /// Creates a new event using the specified handler delegate. - /// - /// The event handler delegate. - public OpenIddictValidationHandler(Func handler) - => _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + /// The event handler delegate. + public OpenIddictValidationHandler(Func handler) + => _handler = handler ?? throw new ArgumentNullException(nameof(handler)); - /// - /// Processes the event. - /// - /// The event to process. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public ValueTask HandleAsync(TContext context) - => _handler(context ?? throw new ArgumentNullException(nameof(context))); - } + /// + /// Processes the event. + /// + /// The event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public ValueTask HandleAsync(TContext context) + => _handler(context ?? throw new ArgumentNullException(nameof(context))); } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlerDescriptor.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlerDescriptor.cs index 8a0bce77..22e0f68d 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlerDescriptor.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlerDescriptor.cs @@ -14,274 +14,273 @@ using Microsoft.Extensions.DependencyInjection; using static OpenIddict.Validation.OpenIddictValidationEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Represents an immutable descriptor of an OpenIddict validation event handler. +/// +[DebuggerDisplay("{ServiceDescriptor?.ServiceType}")] +public class OpenIddictValidationHandlerDescriptor { /// - /// Represents an immutable descriptor of an OpenIddict validation event handler. + /// Creates a new instance of the class. /// - [DebuggerDisplay("{ServiceDescriptor?.ServiceType}")] - public class OpenIddictValidationHandlerDescriptor - { - /// - /// Creates a new instance of the class. - /// - private OpenIddictValidationHandlerDescriptor() { } + private OpenIddictValidationHandlerDescriptor() { } - /// - /// Gets the context type associated with the event. - /// - public Type ContextType { get; private set; } = default!; + /// + /// Gets the context type associated with the event. + /// + public Type ContextType { get; private set; } = default!; - /// - /// Gets the list of filters responsible of excluding the handler - /// from the activated handlers if it doesn't meet the criteria. - /// - public ImmutableArray FilterTypes { get; private set; } = ImmutableArray.Create(); + /// + /// Gets the list of filters responsible of excluding the handler + /// from the activated handlers if it doesn't meet the criteria. + /// + public ImmutableArray FilterTypes { get; private set; } = ImmutableArray.Create(); - /// - /// Gets the order assigned to the handler. - /// - public int Order { get; private set; } + /// + /// Gets the order assigned to the handler. + /// + public int Order { get; private set; } - /// - /// Gets the service descriptor associated with the handler. - /// - public ServiceDescriptor ServiceDescriptor { get; private set; } = default!; + /// + /// Gets the service descriptor associated with the handler. + /// + public ServiceDescriptor ServiceDescriptor { get; private set; } = default!; + + /// + /// Gets the type associated with the handler. + /// + public OpenIddictValidationHandlerType Type { get; private set; } + + /// + /// Creates a builder allowing to initialize an immutable descriptor. + /// + /// The event context type. + /// A new descriptor builder. + public static Builder CreateBuilder() where TContext : BaseContext + => new Builder(); + + /// + /// Contains methods allowing to build a descriptor instance. + /// + /// The event context type. + public class Builder where TContext : BaseContext + { + private ServiceDescriptor? _descriptor; + private readonly List _filters = new(); + private int _order; + private OpenIddictValidationHandlerType _type; /// - /// Gets the type associated with the handler. + /// Adds the type of a handler filter to the filters list. /// - public OpenIddictValidationHandlerType Type { get; private set; } + /// The event handler filter type. + /// The builder instance, so that calls can be easily chained. + public Builder AddFilter(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!typeof(IOpenIddictValidationHandlerFilter<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0104)); + } + + _filters.Add(type); + + return this; + } /// - /// Creates a builder allowing to initialize an immutable descriptor. + /// Adds the type of a handler filter to the filters list. /// - /// The event context type. - /// A new descriptor builder. - public static Builder CreateBuilder() where TContext : BaseContext - => new Builder(); + /// The event handler filter type. + /// The builder instance, so that calls can be easily chained. + public Builder AddFilter() + where TFilter : IOpenIddictValidationHandlerFilter + => AddFilter(typeof(TFilter)); /// - /// Contains methods allowing to build a descriptor instance. + /// Imports the properties set on the specified descriptor. /// - /// The event context type. - public class Builder where TContext : BaseContext + /// The existing descriptor properties are copied from. + /// All the properties previously set on this instance are automatically replaced. + /// The builder instance, so that calls can be easily chained. + public Builder Import(OpenIddictValidationHandlerDescriptor descriptor) { - private ServiceDescriptor? _descriptor; - private readonly List _filters = new(); - private int _order; - private OpenIddictValidationHandlerType _type; - - /// - /// Adds the type of a handler filter to the filters list. - /// - /// The event handler filter type. - /// The builder instance, so that calls can be easily chained. - public Builder AddFilter(Type type) + if (descriptor is null) { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } + throw new ArgumentNullException(nameof(descriptor)); + } - if (!typeof(IOpenIddictValidationHandlerFilter<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0104)); - } + if (descriptor.ContextType != typeof(TContext)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0284)); + } - _filters.Add(type); + _descriptor = descriptor.ServiceDescriptor; + _filters.Clear(); + _filters.AddRange(descriptor.FilterTypes); + _order = descriptor.Order; + _type = descriptor.Type; - return this; - } + return this; + } - /// - /// Adds the type of a handler filter to the filters list. - /// - /// The event handler filter type. - /// The builder instance, so that calls can be easily chained. - public Builder AddFilter() - where TFilter : IOpenIddictValidationHandlerFilter - => AddFilter(typeof(TFilter)); - - /// - /// Imports the properties set on the specified descriptor. - /// - /// The existing descriptor properties are copied from. - /// All the properties previously set on this instance are automatically replaced. - /// The builder instance, so that calls can be easily chained. - public Builder Import(OpenIddictValidationHandlerDescriptor descriptor) + /// + /// Sets the service descriptor. + /// + /// The service descriptor. + /// The builder instance, so that calls can be easily chained. + public Builder SetServiceDescriptor(ServiceDescriptor descriptor) + { + if (descriptor is null) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } - - if (descriptor.ContextType != typeof(TContext)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0284)); - } - - _descriptor = descriptor.ServiceDescriptor; - _filters.Clear(); - _filters.AddRange(descriptor.FilterTypes); - _order = descriptor.Order; - _type = descriptor.Type; - - return this; + throw new ArgumentNullException(nameof(descriptor)); } - /// - /// Sets the service descriptor. - /// - /// The service descriptor. - /// The builder instance, so that calls can be easily chained. - public Builder SetServiceDescriptor(ServiceDescriptor descriptor) + var type = descriptor.ServiceType; + if (!typeof(IOpenIddictValidationHandler<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) { - if (descriptor is null) - { - throw new ArgumentNullException(nameof(descriptor)); - } + throw new InvalidOperationException(SR.GetResourceString(SR.ID0104)); + } - var type = descriptor.ServiceType; - if (!typeof(IOpenIddictValidationHandler<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0104)); - } + _descriptor = descriptor; - _descriptor = descriptor; + return this; + } - return this; - } + /// + /// Sets the order in which the event handler will be invoked. + /// + /// The handler order. + /// The builder instance, so that calls can be easily chained. + public Builder SetOrder(int order) + { + _order = order; - /// - /// Sets the order in which the event handler will be invoked. - /// - /// The handler order. - /// The builder instance, so that calls can be easily chained. - public Builder SetOrder(int order) - { - _order = order; + return this; + } - return this; + /// + /// Sets the type associated to the handler. + /// + /// The handler type. + /// The builder instance, so that calls can be easily chained. + public Builder SetType(OpenIddictValidationHandlerType type) + { + if (!Enum.IsDefined(typeof(OpenIddictValidationHandlerType), type)) + { + throw new InvalidEnumArgumentException(nameof(type), (int) type, typeof(OpenIddictValidationHandlerType)); } - /// - /// Sets the type associated to the handler. - /// - /// The handler type. - /// The builder instance, so that calls can be easily chained. - public Builder SetType(OpenIddictValidationHandlerType type) - { - if (!Enum.IsDefined(typeof(OpenIddictValidationHandlerType), type)) - { - throw new InvalidEnumArgumentException(nameof(type), (int) type, typeof(OpenIddictValidationHandlerType)); - } + _type = type; - _type = type; + return this; + } - return this; + /// + /// Configures the descriptor to use the specified inline handler. + /// + /// The handler instance. + /// The builder instance, so that calls can be easily chained. + public Builder UseInlineHandler(Func handler) + { + if (handler is null) + { + throw new ArgumentNullException(nameof(handler)); } - /// - /// Configures the descriptor to use the specified inline handler. - /// - /// The handler instance. - /// The builder instance, so that calls can be easily chained. - public Builder UseInlineHandler(Func handler) - { - if (handler is null) - { - throw new ArgumentNullException(nameof(handler)); - } + return UseSingletonHandler(new OpenIddictValidationHandler(handler)); + } - return UseSingletonHandler(new OpenIddictValidationHandler(handler)); - } + /// + /// Configures the descriptor to use the specified scoped handler. + /// + /// The handler type. + /// The builder instance, so that calls can be easily chained. + public Builder UseScopedHandler() + where THandler : IOpenIddictValidationHandler + => SetServiceDescriptor(new ServiceDescriptor( + typeof(THandler), typeof(THandler), ServiceLifetime.Scoped)); - /// - /// Configures the descriptor to use the specified scoped handler. - /// - /// The handler type. - /// The builder instance, so that calls can be easily chained. - public Builder UseScopedHandler() - where THandler : IOpenIddictValidationHandler - => SetServiceDescriptor(new ServiceDescriptor( - typeof(THandler), typeof(THandler), ServiceLifetime.Scoped)); - - /// - /// Configures the descriptor to use the specified scoped handler. - /// - /// The handler type. - /// The factory used to create the handler. - /// The builder instance, so that calls can be easily chained. - public Builder UseScopedHandler(Func factory) - where THandler : IOpenIddictValidationHandler + /// + /// Configures the descriptor to use the specified scoped handler. + /// + /// The handler type. + /// The factory used to create the handler. + /// The builder instance, so that calls can be easily chained. + public Builder UseScopedHandler(Func factory) + where THandler : IOpenIddictValidationHandler + { + if (factory is null) { - if (factory is null) - { - throw new ArgumentNullException(nameof(factory)); - } - - return SetServiceDescriptor(new ServiceDescriptor( - typeof(THandler), factory, ServiceLifetime.Scoped)); + throw new ArgumentNullException(nameof(factory)); } - /// - /// Configures the descriptor to use the specified singleton handler. - /// - /// The handler type. - /// The builder instance, so that calls can be easily chained. - public Builder UseSingletonHandler() - where THandler : IOpenIddictValidationHandler - => SetServiceDescriptor(new ServiceDescriptor( - typeof(THandler), typeof(THandler), ServiceLifetime.Singleton)); - - /// - /// Configures the descriptor to use the specified singleton handler. - /// - /// The handler type. - /// The factory used to create the handler. - /// The builder instance, so that calls can be easily chained. - public Builder UseSingletonHandler(Func factory) - where THandler : IOpenIddictValidationHandler - { - if (factory is null) - { - throw new ArgumentNullException(nameof(factory)); - } + return SetServiceDescriptor(new ServiceDescriptor( + typeof(THandler), factory, ServiceLifetime.Scoped)); + } - return SetServiceDescriptor(new ServiceDescriptor( - typeof(THandler), factory, ServiceLifetime.Singleton)); - } + /// + /// Configures the descriptor to use the specified singleton handler. + /// + /// The handler type. + /// The builder instance, so that calls can be easily chained. + public Builder UseSingletonHandler() + where THandler : IOpenIddictValidationHandler + => SetServiceDescriptor(new ServiceDescriptor( + typeof(THandler), typeof(THandler), ServiceLifetime.Singleton)); - /// - /// Configures the descriptor to use the specified singleton handler. - /// - /// The handler type. - /// The handler instance. - /// The builder instance, so that calls can be easily chained. - public Builder UseSingletonHandler(THandler handler) - where THandler : IOpenIddictValidationHandler + /// + /// Configures the descriptor to use the specified singleton handler. + /// + /// The handler type. + /// The factory used to create the handler. + /// The builder instance, so that calls can be easily chained. + public Builder UseSingletonHandler(Func factory) + where THandler : IOpenIddictValidationHandler + { + if (factory is null) { - if (handler is null) - { - throw new ArgumentNullException(nameof(handler)); - } - - return SetServiceDescriptor(new ServiceDescriptor(typeof(THandler), handler)); + throw new ArgumentNullException(nameof(factory)); } - /// - /// Build a new descriptor instance, based on the parameters that were previously set. - /// - /// The builder instance, so that calls can be easily chained. - public OpenIddictValidationHandlerDescriptor Build() => new OpenIddictValidationHandlerDescriptor + return SetServiceDescriptor(new ServiceDescriptor( + typeof(THandler), factory, ServiceLifetime.Singleton)); + } + + /// + /// Configures the descriptor to use the specified singleton handler. + /// + /// The handler type. + /// The handler instance. + /// The builder instance, so that calls can be easily chained. + public Builder UseSingletonHandler(THandler handler) + where THandler : IOpenIddictValidationHandler + { + if (handler is null) { - ContextType = typeof(TContext), - FilterTypes = _filters.ToImmutableArray(), - Order = _order, - ServiceDescriptor = _descriptor ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0105)), - Type = _type - }; + throw new ArgumentNullException(nameof(handler)); + } + + return SetServiceDescriptor(new ServiceDescriptor(typeof(THandler), handler)); } + + /// + /// Build a new descriptor instance, based on the parameters that were previously set. + /// + /// The builder instance, so that calls can be easily chained. + public OpenIddictValidationHandlerDescriptor Build() => new OpenIddictValidationHandlerDescriptor + { + ContextType = typeof(TContext), + FilterTypes = _filters.ToImmutableArray(), + Order = _order, + ServiceDescriptor = _descriptor ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0105)), + Type = _type + }; } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs index 5432e2f8..d0430645 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs @@ -9,89 +9,88 @@ using System.ComponentModel; using System.Threading.Tasks; using static OpenIddict.Validation.OpenIddictValidationEvents; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +[EditorBrowsable(EditorBrowsableState.Advanced)] +public static class OpenIddictValidationHandlerFilters { - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static class OpenIddictValidationHandlerFilters + /// + /// Represents a filter that excludes the associated handlers if no access token is validated. + /// + public class RequireAccessTokenValidated : IOpenIddictValidationHandlerFilter { - /// - /// Represents a filter that excludes the associated handlers if no access token is validated. - /// - public class RequireAccessTokenValidated : IOpenIddictValidationHandlerFilter + public ValueTask IsActiveAsync(ProcessAuthenticationContext context) { - public ValueTask IsActiveAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.ValidateAccessToken); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.ValidateAccessToken); } + } - /// - /// Represents a filter that excludes the associated handlers if authorization validation was not enabled. - /// - public class RequireAuthorizationEntryValidationEnabled : IOpenIddictValidationHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if authorization validation was not enabled. + /// + public class RequireAuthorizationEntryValidationEnabled : IOpenIddictValidationHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Options.EnableAuthorizationEntryValidation); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Options.EnableAuthorizationEntryValidation); } + } - /// - /// Represents a filter that excludes the associated handlers if local validation is not used. - /// - public class RequireLocalValidation : IOpenIddictValidationHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if local validation is not used. + /// + public class RequireLocalValidation : IOpenIddictValidationHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Options.ValidationType == OpenIddictValidationType.Direct); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Options.ValidationType == OpenIddictValidationType.Direct); } + } - /// - /// Represents a filter that excludes the associated handlers if introspection is not used. - /// - public class RequireIntrospectionValidation : IOpenIddictValidationHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if introspection is not used. + /// + public class RequireIntrospectionValidation : IOpenIddictValidationHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Options.ValidationType == OpenIddictValidationType.Introspection); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Options.ValidationType == OpenIddictValidationType.Introspection); } + } - /// - /// Represents a filter that excludes the associated handlers if token validation was not enabled. - /// - public class RequireTokenEntryValidationEnabled : IOpenIddictValidationHandlerFilter + /// + /// Represents a filter that excludes the associated handlers if token validation was not enabled. + /// + public class RequireTokenEntryValidationEnabled : IOpenIddictValidationHandlerFilter + { + public ValueTask IsActiveAsync(BaseContext context) { - public ValueTask IsActiveAsync(BaseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - return new ValueTask(context.Options.EnableTokenEntryValidation); + throw new ArgumentNullException(nameof(context)); } + + return new ValueTask(context.Options.EnableTokenEntryValidation); } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlerType.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlerType.cs index 3ebe5c6b..fd5c282d 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlerType.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlerType.cs @@ -4,26 +4,25 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Represents the type of an OpenIddict validation handler. +/// +public enum OpenIddictValidationHandlerType { /// - /// Represents the type of an OpenIddict validation handler. + /// The handler is of an unspecified type. /// - public enum OpenIddictValidationHandlerType - { - /// - /// The handler is of an unspecified type. - /// - Unknown = 0, + Unknown = 0, - /// - /// The handler is a built-in handler, provided as part of the official OpenIddict packages. - /// - BuiltIn = 1, + /// + /// The handler is a built-in handler, provided as part of the official OpenIddict packages. + /// + BuiltIn = 1, - /// - /// The handler is a custom handler, registered by the end user or a third-party package. - /// - Custom = 2 - } + /// + /// The handler is a custom handler, registered by the end user or a third-party package. + /// + Custom = 2 } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Discovery.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Discovery.cs index 291e69aa..e8b856f1 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Discovery.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Discovery.cs @@ -12,323 +12,322 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Validation.OpenIddictValidationEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public static partial class OpenIddictValidationHandlers { - public static partial class OpenIddictValidationHandlers + public static class Discovery { - public static class Discovery + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Configuration response handling: + */ + HandleErrorResponse.Descriptor, + ValidateIssuer.Descriptor, + ExtractCryptographyEndpoint.Descriptor, + ExtractIntrospectionEndpoint.Descriptor, + + /* + * Cryptography response handling: + */ + HandleErrorResponse.Descriptor, + ExtractSigningKeys.Descriptor); + + /// + /// Contains the logic responsible of extracting the issuer from the discovery document. + /// + public class ValidateIssuer : IOpenIddictValidationHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Configuration response handling: - */ - HandleErrorResponse.Descriptor, - ValidateIssuer.Descriptor, - ExtractCryptographyEndpoint.Descriptor, - ExtractIntrospectionEndpoint.Descriptor, - - /* - * Cryptography response handling: - */ - HandleErrorResponse.Descriptor, - ExtractSigningKeys.Descriptor); - /// - /// Contains the logic responsible of extracting the issuer from the discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateIssuer : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // The issuer returned in the discovery document must exactly match the URL used to access it. - // See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation. - var issuer = (string?) context.Response[Metadata.Issuer]; - if (string.IsNullOrEmpty(issuer)) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2096), - uri: SR.FormatID8000(SR.ID2096)); - - return default; - } + throw new ArgumentNullException(nameof(context)); + } - if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? address)) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2097), - uri: SR.FormatID8000(SR.ID2097)); + // The issuer returned in the discovery document must exactly match the URL used to access it. + // See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation. + var issuer = (string?) context.Response[Metadata.Issuer]; + if (string.IsNullOrEmpty(issuer)) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2096), + uri: SR.FormatID8000(SR.ID2096)); - return default; - } + return default; + } - if (context.Issuer is not null && context.Issuer != address) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2098), - uri: SR.FormatID8000(SR.ID2098)); + if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? address)) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2097), + uri: SR.FormatID8000(SR.ID2097)); - return default; - } + return default; + } - context.Configuration.Issuer = issuer; + if (context.Issuer is not null && context.Issuer != address) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2098), + uri: SR.FormatID8000(SR.ID2098)); return default; } + + context.Configuration.Issuer = issuer; + + return default; } + } + /// + /// Contains the logic responsible of extracting the JWKS endpoint address from the discovery document. + /// + public class ExtractCryptographyEndpoint : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the JWKS endpoint address from the discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractCryptographyEndpoint : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateIssuer.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateIssuer.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Note: the jwks_uri node is required by the OpenID Connect discovery specification. - // See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation. - var address = (string?) context.Response[Metadata.JwksUri]; - if (string.IsNullOrEmpty(address)) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2099), - uri: SR.FormatID8000(SR.ID2099)); - - return default; - } + throw new ArgumentNullException(nameof(context)); + } - if (!Uri.IsWellFormedUriString(address, UriKind.Absolute)) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2100), - uri: SR.FormatID8000(SR.ID2100)); + // Note: the jwks_uri node is required by the OpenID Connect discovery specification. + // See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation. + var address = (string?) context.Response[Metadata.JwksUri]; + if (string.IsNullOrEmpty(address)) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2099), + uri: SR.FormatID8000(SR.ID2099)); - return default; - } + return default; + } - context.Configuration.JwksUri = address; + if (!Uri.IsWellFormedUriString(address, UriKind.Absolute)) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2100), + uri: SR.FormatID8000(SR.ID2100)); return default; } + + context.Configuration.JwksUri = address; + + return default; } + } + /// + /// Contains the logic responsible of extracting the introspection endpoint address from the discovery document. + /// + public class ExtractIntrospectionEndpoint : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the introspection endpoint address from the discovery document. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractIntrospectionEndpoint : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ExtractCryptographyEndpoint.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleConfigurationResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ExtractCryptographyEndpoint.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleConfigurationResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - var address = (string?) context.Response[Metadata.IntrospectionEndpoint]; - if (!string.IsNullOrEmpty(address) && !Uri.IsWellFormedUriString(address, UriKind.Absolute)) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2101), - uri: SR.FormatID8000(SR.ID2101)); + var address = (string?) context.Response[Metadata.IntrospectionEndpoint]; + if (!string.IsNullOrEmpty(address) && !Uri.IsWellFormedUriString(address, UriKind.Absolute)) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2101), + uri: SR.FormatID8000(SR.ID2101)); - return default; - } + return default; + } - context.Configuration.IntrospectionEndpoint = address; + context.Configuration.IntrospectionEndpoint = address; - // Resolve the client authentication methods supported by the introspection endpoint, if available. - if (context.Response.TryGetParameter(Metadata.IntrospectionEndpointAuthMethodsSupported, out var methods)) + // Resolve the client authentication methods supported by the introspection endpoint, if available. + if (context.Response.TryGetParameter(Metadata.IntrospectionEndpointAuthMethodsSupported, out var methods)) + { + foreach (var method in methods.GetUnnamedParameters()) { - foreach (var method in methods.GetUnnamedParameters()) + var value = (string?) method; + if (string.IsNullOrEmpty(value)) { - var value = (string?) method; - if (string.IsNullOrEmpty(value)) - { - continue; - } - - context.Configuration.IntrospectionEndpointAuthMethodsSupported.Add(value); + continue; } - } - return default; + context.Configuration.IntrospectionEndpointAuthMethodsSupported.Add(value); + } } + + return default; } + } + /// + /// Contains the logic responsible of extracting the signing keys from the JWKS document. + /// + public class ExtractSigningKeys : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the signing keys from the JWKS document. + /// Gets the default descriptor definition assigned to this handler. /// - public class ExtractSigningKeys : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleCryptographyResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleCryptographyResponseContext context) + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + var keys = context.Response[JsonWebKeySetParameterNames.Keys]?.GetUnnamedParameters(); + if (keys is null || keys.Count == 0) + { + context.Reject( + error: Errors.ServerError, + description: SR.FormatID2102(JsonWebKeySetParameterNames.Keys), + uri: SR.FormatID8000(SR.ID2102)); + + return default; + } + + for (var index = 0; index < keys.Count; index++) { - if (context is null) + // Note: the "use" parameter is defined as optional by the specification. + // To prevent key swapping attacks, OpenIddict requires that this parameter + // be present and will ignore keys that don't include a "use" parameter. + var use = (string?) keys[index][JsonWebKeyParameterNames.Use]; + if (string.IsNullOrEmpty(use)) { - throw new ArgumentNullException(nameof(context)); + continue; } - var keys = context.Response[JsonWebKeySetParameterNames.Keys]?.GetUnnamedParameters(); - if (keys is null || keys.Count == 0) + // Ignore security keys that are not used for signing. + if (!string.Equals(use, JsonWebKeyUseNames.Sig, StringComparison.Ordinal)) { - context.Reject( - error: Errors.ServerError, - description: SR.FormatID2102(JsonWebKeySetParameterNames.Keys), - uri: SR.FormatID8000(SR.ID2102)); - - return default; + continue; } - for (var index = 0; index < keys.Count; index++) + var key = (string?) keys[index][JsonWebKeyParameterNames.Kty] switch { - // Note: the "use" parameter is defined as optional by the specification. - // To prevent key swapping attacks, OpenIddict requires that this parameter - // be present and will ignore keys that don't include a "use" parameter. - var use = (string?) keys[index][JsonWebKeyParameterNames.Use]; - if (string.IsNullOrEmpty(use)) - { - continue; - } - - // Ignore security keys that are not used for signing. - if (!string.Equals(use, JsonWebKeyUseNames.Sig, StringComparison.Ordinal)) + JsonWebAlgorithmsKeyTypes.RSA => new JsonWebKey { - continue; - } + Kty = JsonWebAlgorithmsKeyTypes.RSA, + E = (string?) keys[index][JsonWebKeyParameterNames.E], + N = (string?) keys[index][JsonWebKeyParameterNames.N] + }, - var key = (string?) keys[index][JsonWebKeyParameterNames.Kty] switch + JsonWebAlgorithmsKeyTypes.EllipticCurve => new JsonWebKey { - JsonWebAlgorithmsKeyTypes.RSA => new JsonWebKey - { - Kty = JsonWebAlgorithmsKeyTypes.RSA, - E = (string?) keys[index][JsonWebKeyParameterNames.E], - N = (string?) keys[index][JsonWebKeyParameterNames.N] - }, - - JsonWebAlgorithmsKeyTypes.EllipticCurve => new JsonWebKey - { - Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve, - Crv = (string?) keys[index][JsonWebKeyParameterNames.Crv], - X = (string?) keys[index][JsonWebKeyParameterNames.X], - Y = (string?) keys[index][JsonWebKeyParameterNames.Y] - }, + Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve, + Crv = (string?) keys[index][JsonWebKeyParameterNames.Crv], + X = (string?) keys[index][JsonWebKeyParameterNames.X], + Y = (string?) keys[index][JsonWebKeyParameterNames.Y] + }, - _ => null - }; + _ => null + }; - if (key is null) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2103), - uri: SR.FormatID8000(SR.ID2103)); + if (key is null) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2103), + uri: SR.FormatID8000(SR.ID2103)); - return default; - } + return default; + } - // If the key is a RSA key, ensure the mandatory parameters are all present. - if (string.Equals(key.Kty, JsonWebAlgorithmsKeyTypes.RSA, StringComparison.Ordinal) && - (string.IsNullOrEmpty(key.E) || string.IsNullOrEmpty(key.N))) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2104), - uri: SR.FormatID8000(SR.ID2104)); + // If the key is a RSA key, ensure the mandatory parameters are all present. + if (string.Equals(key.Kty, JsonWebAlgorithmsKeyTypes.RSA, StringComparison.Ordinal) && + (string.IsNullOrEmpty(key.E) || string.IsNullOrEmpty(key.N))) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2104), + uri: SR.FormatID8000(SR.ID2104)); - return default; - } + return default; + } - // If the key is an EC key, ensure the mandatory parameters are all present. - if (string.Equals(key.Kty, JsonWebAlgorithmsKeyTypes.EllipticCurve, StringComparison.Ordinal) && - (string.IsNullOrEmpty(key.Crv) || string.IsNullOrEmpty(key.X) || string.IsNullOrEmpty(key.Y))) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2104), - uri: SR.FormatID8000(SR.ID2104)); + // If the key is an EC key, ensure the mandatory parameters are all present. + if (string.Equals(key.Kty, JsonWebAlgorithmsKeyTypes.EllipticCurve, StringComparison.Ordinal) && + (string.IsNullOrEmpty(key.Crv) || string.IsNullOrEmpty(key.X) || string.IsNullOrEmpty(key.Y))) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2104), + uri: SR.FormatID8000(SR.ID2104)); - return default; - } + return default; + } - key.KeyId = (string?) keys[index][JsonWebKeyParameterNames.Kid]; - key.X5t = (string?) keys[index][JsonWebKeyParameterNames.X5t]; - key.X5tS256 = (string?) keys[index][JsonWebKeyParameterNames.X5tS256]; + key.KeyId = (string?) keys[index][JsonWebKeyParameterNames.Kid]; + key.X5t = (string?) keys[index][JsonWebKeyParameterNames.X5t]; + key.X5tS256 = (string?) keys[index][JsonWebKeyParameterNames.X5tS256]; - if (keys[index].TryGetNamedParameter(JsonWebKeyParameterNames.X5c, out var chain)) + if (keys[index].TryGetNamedParameter(JsonWebKeyParameterNames.X5c, out var chain)) + { + foreach (string? certificate in chain.GetUnnamedParameters()) { - foreach (string? certificate in chain.GetUnnamedParameters()) + if (string.IsNullOrEmpty(certificate)) { - if (string.IsNullOrEmpty(certificate)) - { - continue; - } - - key.X5c.Add(certificate); + continue; } - } - context.SecurityKeys.Keys.Add(key); + key.X5c.Add(certificate); + } } - return default; + context.SecurityKeys.Keys.Add(key); } + + return default; } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs index c992aa7d..80b73e8a 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs @@ -16,453 +16,452 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Validation.OpenIddictValidationEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public static partial class OpenIddictValidationHandlers { - public static partial class OpenIddictValidationHandlers + public static class Introspection { - public static class Introspection + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Introspection response handling: + */ + AttachCredentials.Descriptor, + AttachToken.Descriptor, + + /* + * Introspection response handling: + */ + HandleErrorResponse.Descriptor, + HandleInactiveResponse.Descriptor, + ValidateWellKnownClaims.Descriptor, + ValidateIssuer.Descriptor, + ValidateTokenUsage.Descriptor, + PopulateClaims.Descriptor); + + /// + /// Contains the logic responsible of attaching the client credentials to the introspection request. + /// + public class AttachCredentials : IOpenIddictValidationHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Introspection response handling: - */ - AttachCredentials.Descriptor, - AttachToken.Descriptor, - - /* - * Introspection response handling: - */ - HandleErrorResponse.Descriptor, - HandleInactiveResponse.Descriptor, - ValidateWellKnownClaims.Descriptor, - ValidateIssuer.Descriptor, - ValidateTokenUsage.Descriptor, - PopulateClaims.Descriptor); - /// - /// Contains the logic responsible of attaching the client credentials to the introspection request. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachCredentials : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(PrepareIntrospectionRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(PrepareIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.Request.ClientId = context.Options.ClientId; - context.Request.ClientSecret = context.Options.ClientSecret; + context.Request.ClientId = context.Options.ClientId; + context.Request.ClientSecret = context.Options.ClientSecret; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of attaching the token to the introspection request. + /// + public class AttachToken : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of attaching the token to the introspection request. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachToken : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(AttachCredentials.Descriptor.Order + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(PrepareIntrospectionRequestContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(AttachCredentials.Descriptor.Order + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(PrepareIntrospectionRequestContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.Request.Token = context.Token; - context.Request.TokenTypeHint = context.TokenTypeHint; + context.Request.Token = context.Token; + context.Request.TokenTypeHint = context.TokenTypeHint; - return default; - } + return default; } + } + /// + /// Contains the logic responsible of extracting the active: false marker from the response. + /// + public class HandleInactiveResponse : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the active: false marker from the response. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleInactiveResponse : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleIntrospectionResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleIntrospectionResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Note: the introspection specification requires that server return "active: false" instead of a proper - // OAuth 2.0 error when the token is invalid, expired, revoked or invalid for any other reason. - // While OpenIddict's server can be tweaked to return a proper error (by removing NormalizeErrorResponse) - // from the enabled handlers, supporting "active: false" is required to ensure total compatibility. + throw new ArgumentNullException(nameof(context)); + } - if (!context.Response.TryGetParameter(Parameters.Active, out var parameter)) - { - context.Reject( - error: Errors.ServerError, - description: SR.FormatID2105(Parameters.Active), - uri: SR.FormatID8000(SR.ID2105)); + // Note: the introspection specification requires that server return "active: false" instead of a proper + // OAuth 2.0 error when the token is invalid, expired, revoked or invalid for any other reason. + // While OpenIddict's server can be tweaked to return a proper error (by removing NormalizeErrorResponse) + // from the enabled handlers, supporting "active: false" is required to ensure total compatibility. - return default; - } + if (!context.Response.TryGetParameter(Parameters.Active, out var parameter)) + { + context.Reject( + error: Errors.ServerError, + description: SR.FormatID2105(Parameters.Active), + uri: SR.FormatID8000(SR.ID2105)); - // Note: if the parameter cannot be converted to a boolean instance, the default value - // (false) is returned by the static operator, which is appropriate for this check. - if (!(bool) parameter) - { - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2106), - uri: SR.FormatID8000(SR.ID2106)); + return default; + } - return default; - } + // Note: if the parameter cannot be converted to a boolean instance, the default value + // (false) is returned by the static operator, which is appropriate for this check. + if (!(bool) parameter) + { + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2106), + uri: SR.FormatID8000(SR.ID2106)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of validating the well-known claims contained in the introspection response. + /// + public class ValidateWellKnownClaims : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of validating the well-known claims contained in the introspection response. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateWellKnownClaims : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(HandleInactiveResponse.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleIntrospectionResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(HandleInactiveResponse.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleIntrospectionResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - foreach (var parameter in context.Response.GetParameters()) + foreach (var parameter in context.Response.GetParameters()) + { + if (ValidateClaimType(parameter.Key, parameter.Value.Value)) { - if (ValidateClaimType(parameter.Key, parameter.Value.Value)) - { - continue; - } - - context.Reject( - error: Errors.ServerError, - description: SR.FormatID2107(parameter.Key), - uri: SR.FormatID8000(SR.ID2107)); - - return default; + continue; } + context.Reject( + error: Errors.ServerError, + description: SR.FormatID2107(parameter.Key), + uri: SR.FormatID8000(SR.ID2107)); + return default; + } - static bool ValidateClaimType(string name, object? value) => name switch - { - // The 'aud' claim MUST be represented either as a unique string or as an array of multiple strings. - Claims.Audience when value is string or string[] => true, - Claims.Audience when value is JsonElement { ValueKind: JsonValueKind.String } => true, - Claims.Audience when value is JsonElement { ValueKind: JsonValueKind.Array } element && - ValidateArrayChildren(element, JsonValueKind.String) => true, - Claims.Audience => false, - - // The 'exp', 'iat' and 'nbf' claims MUST be formatted as numeric date values. - Claims.ExpiresAt or Claims.IssuedAt or Claims.NotBefore - => value is long or JsonElement { ValueKind: JsonValueKind.Number }, - - // The 'jti', 'iss', 'scope' and 'token_usage' claims MUST be formatted as a unique string. - Claims.JwtId or Claims.Issuer or Claims.Scope or Claims.TokenUsage - => value is string or JsonElement { ValueKind: JsonValueKind.String }, - - // Claims that are not in the well-known list can be of any type. - _ => true - }; - - static bool ValidateArrayChildren(JsonElement element, JsonValueKind kind) + return default; + + static bool ValidateClaimType(string name, object? value) => name switch + { + // The 'aud' claim MUST be represented either as a unique string or as an array of multiple strings. + Claims.Audience when value is string or string[] => true, + Claims.Audience when value is JsonElement { ValueKind: JsonValueKind.String } => true, + Claims.Audience when value is JsonElement { ValueKind: JsonValueKind.Array } element && + ValidateArrayChildren(element, JsonValueKind.String) => true, + Claims.Audience => false, + + // The 'exp', 'iat' and 'nbf' claims MUST be formatted as numeric date values. + Claims.ExpiresAt or Claims.IssuedAt or Claims.NotBefore + => value is long or JsonElement { ValueKind: JsonValueKind.Number }, + + // The 'jti', 'iss', 'scope' and 'token_usage' claims MUST be formatted as a unique string. + Claims.JwtId or Claims.Issuer or Claims.Scope or Claims.TokenUsage + => value is string or JsonElement { ValueKind: JsonValueKind.String }, + + // Claims that are not in the well-known list can be of any type. + _ => true + }; + + static bool ValidateArrayChildren(JsonElement element, JsonValueKind kind) + { + foreach (var child in element.EnumerateArray()) { - foreach (var child in element.EnumerateArray()) + if (child.ValueKind != kind) { - if (child.ValueKind != kind) - { - return false; - } + return false; } - - return true; } + + return true; } } + } + /// + /// Contains the logic responsible of extracting the issuer from the introspection response. + /// + public class ValidateIssuer : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the issuer from the introspection response. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateIssuer : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateWellKnownClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleIntrospectionResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateWellKnownClaims.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleIntrospectionResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // The issuer claim is optional. If it's not null or empty, validate it to - // ensure it matches the issuer registered in the server configuration. - var issuer = (string?) context.Response[Claims.Issuer]; - if (!string.IsNullOrEmpty(issuer)) + // The issuer claim is optional. If it's not null or empty, validate it to + // ensure it matches the issuer registered in the server configuration. + var issuer = (string?) context.Response[Claims.Issuer]; + if (!string.IsNullOrEmpty(issuer)) + { + if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? uri)) { - if (!Uri.TryCreate(issuer, UriKind.Absolute, out Uri? uri)) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2108), - uri: SR.FormatID8000(SR.ID2108)); + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2108), + uri: SR.FormatID8000(SR.ID2108)); - return default; - } + return default; + } - if (context.Issuer is not null && context.Issuer != uri) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2109), - uri: SR.FormatID8000(SR.ID2109)); + if (context.Issuer is not null && context.Issuer != uri) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2109), + uri: SR.FormatID8000(SR.ID2109)); - return default; - } + return default; } - - return default; } + + return default; } + } + /// + /// Contains the logic responsible of extracting and validating the token usage from the introspection response. + /// + public class ValidateTokenUsage : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting and validating the token usage from the introspection response. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTokenUsage : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateIssuer.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleIntrospectionResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateIssuer.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleIntrospectionResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // OpenIddict-based authorization servers always return the actual token type using - // the special "token_usage" claim, that helps resource servers determine whether the - // introspected token is of the expected type and prevent token substitution attacks. - // In this handler, the "token_usage" is verified to ensure it corresponds to a supported - // value so that the component that triggered the introspection request can determine - // whether the returned token has an acceptable type depending on the context. - var usage = (string?) context.Response[Claims.TokenUsage]; - if (string.IsNullOrEmpty(usage)) - { - return default; - } + throw new ArgumentNullException(nameof(context)); + } - if (!(usage switch - { - // Note: by default, OpenIddict only allows access/refresh tokens to be - // introspected but additional types can be added using the events model. - TokenTypeHints.AccessToken or TokenTypeHints.AuthorizationCode or - TokenTypeHints.IdToken or TokenTypeHints.RefreshToken or - TokenTypeHints.UserCode - => true, - - _ => false // Other token usages are not supported. - })) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2118), - uri: SR.FormatID8000(SR.ID2118)); + // OpenIddict-based authorization servers always return the actual token type using + // the special "token_usage" claim, that helps resource servers determine whether the + // introspected token is of the expected type and prevent token substitution attacks. + // In this handler, the "token_usage" is verified to ensure it corresponds to a supported + // value so that the component that triggered the introspection request can determine + // whether the returned token has an acceptable type depending on the context. + var usage = (string?) context.Response[Claims.TokenUsage]; + if (string.IsNullOrEmpty(usage)) + { + return default; + } - return default; - } + if (!(usage switch + { + // Note: by default, OpenIddict only allows access/refresh tokens to be + // introspected but additional types can be added using the events model. + TokenTypeHints.AccessToken or TokenTypeHints.AuthorizationCode or + TokenTypeHints.IdToken or TokenTypeHints.RefreshToken or + TokenTypeHints.UserCode + => true, + + _ => false // Other token usages are not supported. + })) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2118), + uri: SR.FormatID8000(SR.ID2118)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of extracting the claims from the introspection response. + /// + public class PopulateClaims : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of extracting the claims from the introspection response. + /// Gets the default descriptor definition assigned to this handler. /// - public class PopulateClaims : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateTokenUsage.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(HandleIntrospectionResponseContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateTokenUsage.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(HandleIntrospectionResponseContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - // Create a new claims-based identity using the same authentication type - // and the name/role claims as the one used by IdentityModel for JWT tokens. - var identity = new ClaimsIdentity( - context.Options.TokenValidationParameters.AuthenticationType, - context.Options.TokenValidationParameters.NameClaimType, - context.Options.TokenValidationParameters.RoleClaimType); + // Create a new claims-based identity using the same authentication type + // and the name/role claims as the one used by IdentityModel for JWT tokens. + var identity = new ClaimsIdentity( + context.Options.TokenValidationParameters.AuthenticationType, + context.Options.TokenValidationParameters.NameClaimType, + context.Options.TokenValidationParameters.RoleClaimType); - // Resolve the issuer that will be attached to the claims created by this handler. - // Note: at this stage, the optional issuer extracted from the response is assumed - // to be valid, as it is guarded against unknown values by the ValidateIssuer handler. - var issuer = (string?) context.Response[Claims.Issuer] ?? context.Issuer?.AbsoluteUri ?? ClaimsIdentity.DefaultIssuer; + // Resolve the issuer that will be attached to the claims created by this handler. + // Note: at this stage, the optional issuer extracted from the response is assumed + // to be valid, as it is guarded against unknown values by the ValidateIssuer handler. + var issuer = (string?) context.Response[Claims.Issuer] ?? context.Issuer?.AbsoluteUri ?? ClaimsIdentity.DefaultIssuer; - foreach (var parameter in context.Response.GetParameters()) + foreach (var parameter in context.Response.GetParameters()) + { + // Always exclude null keys and values, as they can't be represented as valid claims. + if (string.IsNullOrEmpty(parameter.Key) || OpenIddictParameter.IsNullOrEmpty(parameter.Value)) { - // Always exclude null keys and values, as they can't be represented as valid claims. - if (string.IsNullOrEmpty(parameter.Key) || OpenIddictParameter.IsNullOrEmpty(parameter.Value)) - { - continue; - } + continue; + } - // Exclude OpenIddict-specific private claims, that MUST NOT be set based on data returned - // by the remote authorization server (that may or may not be an OpenIddict server). - if (parameter.Key.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) - { - continue; - } + // Exclude OpenIddict-specific private claims, that MUST NOT be set based on data returned + // by the remote authorization server (that may or may not be an OpenIddict server). + if (parameter.Key.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) + { + continue; + } - // Ignore all protocol claims that shouldn't be mapped to CLR claims. - if (parameter.Key is Claims.Active or Claims.Issuer or Claims.NotBefore or Claims.TokenType) - { - continue; - } + // Ignore all protocol claims that shouldn't be mapped to CLR claims. + if (parameter.Key is Claims.Active or Claims.Issuer or Claims.NotBefore or Claims.TokenType) + { + continue; + } - switch (parameter.Value.Value) - { - // Claims represented as arrays are split and mapped to multiple CLR claims. - case JsonElement { ValueKind: JsonValueKind.Array } value: - foreach (var element in value.EnumerateArray()) + switch (parameter.Value.Value) + { + // Claims represented as arrays are split and mapped to multiple CLR claims. + case JsonElement { ValueKind: JsonValueKind.Array } value: + foreach (var element in value.EnumerateArray()) + { + var item = element.GetString(); + if (string.IsNullOrEmpty(item)) { - var item = element.GetString(); - if (string.IsNullOrEmpty(item)) - { - continue; - } - - identity.AddClaim(new Claim(parameter.Key, item, - GetClaimValueType(value.ValueKind), issuer, issuer, identity)); + continue; } - break; - case JsonElement value: - identity.AddClaim(new Claim(parameter.Key, value.ToString()!, + identity.AddClaim(new Claim(parameter.Key, item, GetClaimValueType(value.ValueKind), issuer, issuer, identity)); - break; - - // Note: in the typical case, the introspection parameters should be deserialized from - // a JSON response and thus represented as System.Text.Json.JsonElement instances. - // However, to support responses resolved from custom locations and parameters manually added - // by the application using the events model, the CLR primitive types are also supported. - - case bool value: - identity.AddClaim(new Claim(parameter.Key, value.ToString(), - ClaimValueTypes.Boolean, issuer, issuer, identity)); - break; - - case long value: - identity.AddClaim(new Claim(parameter.Key, value.ToString(CultureInfo.InvariantCulture), - ClaimValueTypes.Integer64, issuer, issuer, identity)); - break; - - case string value: - identity.AddClaim(new Claim(parameter.Key, value, ClaimValueTypes.String, issuer, issuer, identity)); - break; - - // Claims represented as arrays are split and mapped to multiple CLR claims. - case string[] value: - for (var index = 0; index < value.Length; index++) - { - identity.AddClaim(new Claim(parameter.Key, value[index], ClaimValueTypes.String, issuer, issuer, identity)); - } - break; - } + } + break; + + case JsonElement value: + identity.AddClaim(new Claim(parameter.Key, value.ToString()!, + GetClaimValueType(value.ValueKind), issuer, issuer, identity)); + break; + + // Note: in the typical case, the introspection parameters should be deserialized from + // a JSON response and thus represented as System.Text.Json.JsonElement instances. + // However, to support responses resolved from custom locations and parameters manually added + // by the application using the events model, the CLR primitive types are also supported. + + case bool value: + identity.AddClaim(new Claim(parameter.Key, value.ToString(), + ClaimValueTypes.Boolean, issuer, issuer, identity)); + break; + + case long value: + identity.AddClaim(new Claim(parameter.Key, value.ToString(CultureInfo.InvariantCulture), + ClaimValueTypes.Integer64, issuer, issuer, identity)); + break; + + case string value: + identity.AddClaim(new Claim(parameter.Key, value, ClaimValueTypes.String, issuer, issuer, identity)); + break; + + // Claims represented as arrays are split and mapped to multiple CLR claims. + case string[] value: + for (var index = 0; index < value.Length; index++) + { + identity.AddClaim(new Claim(parameter.Key, value[index], ClaimValueTypes.String, issuer, issuer, identity)); + } + break; } + } - context.Principal = new ClaimsPrincipal(identity); + context.Principal = new ClaimsPrincipal(identity); - return default; + return default; - static string GetClaimValueType(JsonValueKind kind) => kind switch - { - JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean, + static string GetClaimValueType(JsonValueKind kind) => kind switch + { + JsonValueKind.True or JsonValueKind.False => ClaimValueTypes.Boolean, - JsonValueKind.String => ClaimValueTypes.String, - JsonValueKind.Number => ClaimValueTypes.Integer64, + JsonValueKind.String => ClaimValueTypes.String, + JsonValueKind.Number => ClaimValueTypes.Integer64, - JsonValueKind.Array => JsonClaimValueTypes.JsonArray, - JsonValueKind.Object or _ => JsonClaimValueTypes.Json - }; - } + JsonValueKind.Array => JsonClaimValueTypes.JsonArray, + JsonValueKind.Object or _ => JsonClaimValueTypes.Json + }; } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs index 11039624..e805d53b 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs @@ -19,845 +19,844 @@ using static OpenIddict.Validation.OpenIddictValidationEvents; using static OpenIddict.Validation.OpenIddictValidationHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public static partial class OpenIddictValidationHandlers { - public static partial class OpenIddictValidationHandlers + public static class Protection { - public static class Protection + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Token validation: + */ + ResolveTokenValidationParameters.Descriptor, + ValidateReferenceTokenIdentifier.Descriptor, + ValidateIdentityModelToken.Descriptor, + IntrospectToken.Descriptor, + NormalizeScopeClaims.Descriptor, + MapInternalClaims.Descriptor, + RestoreReferenceTokenProperties.Descriptor, + ValidatePrincipal.Descriptor, + ValidateExpirationDate.Descriptor, + ValidateAudience.Descriptor, + ValidateTokenEntry.Descriptor, + ValidateAuthorizationEntry.Descriptor); + + /// + /// Contains the logic responsible of resolving the validation parameters used to validate tokens. + /// + public class ResolveTokenValidationParameters : IOpenIddictValidationHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Token validation: - */ - ResolveTokenValidationParameters.Descriptor, - ValidateReferenceTokenIdentifier.Descriptor, - ValidateIdentityModelToken.Descriptor, - IntrospectToken.Descriptor, - NormalizeScopeClaims.Descriptor, - MapInternalClaims.Descriptor, - RestoreReferenceTokenProperties.Descriptor, - ValidatePrincipal.Descriptor, - ValidateExpirationDate.Descriptor, - ValidateAudience.Descriptor, - ValidateTokenEntry.Descriptor, - ValidateAuthorizationEntry.Descriptor); - /// - /// Contains the logic responsible of resolving the validation parameters used to validate tokens. + /// Gets the default descriptor definition assigned to this handler. /// - public class ResolveTokenValidationParameters : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); - // Clone the token validation parameters and set the issuer using the value found in the - // OpenID Connect server configuration (that can be static or retrieved using discovery). - var parameters = context.Options.TokenValidationParameters.Clone(); - parameters.ValidIssuer ??= configuration.Issuer ?? context.Issuer?.AbsoluteUri; - parameters.ValidateIssuer = !string.IsNullOrEmpty(parameters.ValidIssuer); + // Clone the token validation parameters and set the issuer using the value found in the + // OpenID Connect server configuration (that can be static or retrieved using discovery). + var parameters = context.Options.TokenValidationParameters.Clone(); + parameters.ValidIssuer ??= configuration.Issuer ?? context.Issuer?.AbsoluteUri; + parameters.ValidateIssuer = !string.IsNullOrEmpty(parameters.ValidIssuer); - // Combine the signing keys registered statically in the token validation parameters - // with the signing keys resolved from the OpenID Connect server configuration. - parameters.IssuerSigningKeys = - parameters.IssuerSigningKeys?.Concat(configuration.SigningKeys) ?? configuration.SigningKeys; + // Combine the signing keys registered statically in the token validation parameters + // with the signing keys resolved from the OpenID Connect server configuration. + parameters.IssuerSigningKeys = + parameters.IssuerSigningKeys?.Concat(configuration.SigningKeys) ?? configuration.SigningKeys; - parameters.ValidTypes = context.ValidTokenTypes.Count switch - { - // If no specific token type is expected, accept all token types at this stage. - // Additional filtering can be made based on the resolved/actual token type. - 0 => null, + parameters.ValidTypes = context.ValidTokenTypes.Count switch + { + // If no specific token type is expected, accept all token types at this stage. + // Additional filtering can be made based on the resolved/actual token type. + 0 => null, - // Otherwise, map the token types to their JWT public or internal representation. - _ => context.ValidTokenTypes.SelectMany(type => type switch + // Otherwise, map the token types to their JWT public or internal representation. + _ => context.ValidTokenTypes.SelectMany(type => type switch + { + // For access tokens, both "at+jwt" and "application/at+jwt" are valid. + TokenTypeHints.AccessToken => new[] { - // For access tokens, both "at+jwt" and "application/at+jwt" are valid. - TokenTypeHints.AccessToken => new[] - { - JsonWebTokenTypes.AccessToken, - JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken - }, + JsonWebTokenTypes.AccessToken, + JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken + }, - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) - }) - }; + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) + }) + }; - context.SecurityTokenHandler = context.Options.JsonWebTokenHandler; - context.TokenValidationParameters = parameters; - } + context.SecurityTokenHandler = context.Options.JsonWebTokenHandler; + context.TokenValidationParameters = parameters; } + } - /// - /// Contains the logic responsible of validating reference token identifiers. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class ValidateReferenceTokenIdentifier : IOpenIddictValidationHandler - { - private readonly IOpenIddictTokenManager _tokenManager; - - public ValidateReferenceTokenIdentifier() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); + /// + /// Contains the logic responsible of validating reference token identifiers. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateReferenceTokenIdentifier : IOpenIddictValidationHandler + { + private readonly IOpenIddictTokenManager _tokenManager; - public ValidateReferenceTokenIdentifier(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + public ValidateReferenceTokenIdentifier() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ResolveTokenValidationParameters.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); + public ValidateReferenceTokenIdentifier(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; - /// - public async ValueTask HandleAsync(ValidateTokenContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ResolveTokenValidationParameters.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // Reference tokens are base64url-encoded payloads of exactly 256 bits (generated using a - // crypto-secure RNG). If the token length differs, the token cannot be a reference token. - if (context.Token.Length != 43) - { - return; - } + throw new ArgumentNullException(nameof(context)); + } - // If the reference token cannot be found, don't return an error to allow another handler to validate it. - var token = await _tokenManager.FindByReferenceIdAsync(context.Token); - if (token is null) - { - return; - } + // Reference tokens are base64url-encoded payloads of exactly 256 bits (generated using a + // crypto-secure RNG). If the token length differs, the token cannot be a reference token. + if (context.Token.Length != 43) + { + return; + } - // If the type associated with the token entry doesn't match one of the expected types, return an error. - if (!(context.ValidTokenTypes.Count switch - { - 0 => true, // If no specific token type is expected, accept all token types at this stage. - 1 => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ElementAt(0)), - _ => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray()) - })) - { - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2004), - uri: SR.FormatID8000(SR.ID2004)); + // If the reference token cannot be found, don't return an error to allow another handler to validate it. + var token = await _tokenManager.FindByReferenceIdAsync(context.Token); + if (token is null) + { + return; + } - return; - } + // If the type associated with the token entry doesn't match one of the expected types, return an error. + if (!(context.ValidTokenTypes.Count switch + { + 0 => true, // If no specific token type is expected, accept all token types at this stage. + 1 => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ElementAt(0)), + _ => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray()) + })) + { + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2004), + uri: SR.FormatID8000(SR.ID2004)); - var payload = await _tokenManager.GetPayloadAsync(token); - if (string.IsNullOrEmpty(payload)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0026)); - } + return; + } - // Replace the token parameter by the payload resolved from the token entry - // and store the identifier of the reference token so it can be later - // used to restore the properties associated with the token. - context.Token = payload; - context.TokenId = await _tokenManager.GetIdAsync(token); + var payload = await _tokenManager.GetPayloadAsync(token); + if (string.IsNullOrEmpty(payload)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0026)); } + + // Replace the token parameter by the payload resolved from the token entry + // and store the identifier of the reference token so it can be later + // used to restore the properties associated with the token. + context.Token = payload; + context.TokenId = await _tokenManager.GetIdAsync(token); } + } + /// + /// Contains the logic responsible of validating tokens generated using IdentityModel. + /// + public class ValidateIdentityModelToken : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of validating tokens generated using IdentityModel. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateIdentityModelToken : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ValidateReferenceTokenIdentifier.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ValidateReferenceTokenIdentifier.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If a principal was already attached, don't overwrite it. - if (context.Principal is not null) - { - return default; - } + // If a principal was already attached, don't overwrite it. + if (context.Principal is not null) + { + return default; + } - // If the token cannot be read, don't return an error to allow another handler to validate it. - if (!context.SecurityTokenHandler.CanReadToken(context.Token)) - { - return default; - } + // If the token cannot be read, don't return an error to allow another handler to validate it. + if (!context.SecurityTokenHandler.CanReadToken(context.Token)) + { + return default; + } - var result = context.SecurityTokenHandler.ValidateToken(context.Token, context.TokenValidationParameters); - if (!result.IsValid) + var result = context.SecurityTokenHandler.ValidateToken(context.Token, context.TokenValidationParameters); + if (!result.IsValid) + { + // If validation failed because of an unrecognized key identifier, inform the configuration manager + // that the configuration MAY have be refreshed by sending a new discovery request to the server. + if (result.Exception is SecurityTokenSignatureKeyNotFoundException) { - // If validation failed because of an unrecognized key identifier, inform the configuration manager - // that the configuration MAY have be refreshed by sending a new discovery request to the server. - if (result.Exception is SecurityTokenSignatureKeyNotFoundException) - { - context.Options.ConfigurationManager.RequestRefresh(); - } - - context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token); - - context.Reject( - error: Errors.InvalidToken, - description: result.Exception switch - { - SecurityTokenInvalidIssuerException => SR.GetResourceString(SR.ID2088), - SecurityTokenInvalidTypeException => SR.GetResourceString(SR.ID2089), - SecurityTokenSignatureKeyNotFoundException => SR.GetResourceString(SR.ID2090), - SecurityTokenInvalidSignatureException => SR.GetResourceString(SR.ID2091), - - _ => SR.GetResourceString(SR.ID2004) - }, - uri: result.Exception switch - { - SecurityTokenInvalidIssuerException => SR.FormatID8000(SR.ID2088), - SecurityTokenInvalidTypeException => SR.FormatID8000(SR.ID2089), - SecurityTokenSignatureKeyNotFoundException => SR.FormatID8000(SR.ID2090), - SecurityTokenInvalidSignatureException => SR.FormatID8000(SR.ID2091), - - _ => SR.FormatID8000(SR.ID2004) - }); - - return default; + context.Options.ConfigurationManager.RequestRefresh(); } - // Attach the principal extracted from the token to the parent event context and store - // the token type (resolved from "typ" or "token_usage") as a special private claim. - context.Principal = new ClaimsPrincipal(result.ClaimsIdentity).SetTokenType(result.TokenType switch - { - null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), - - // Both at+jwt and application/at+jwt are supported for access tokens. - JsonWebTokenTypes.AccessToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken - => TokenTypeHints.AccessToken, + context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token); - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) - }); + context.Reject( + error: Errors.InvalidToken, + description: result.Exception switch + { + SecurityTokenInvalidIssuerException => SR.GetResourceString(SR.ID2088), + SecurityTokenInvalidTypeException => SR.GetResourceString(SR.ID2089), + SecurityTokenSignatureKeyNotFoundException => SR.GetResourceString(SR.ID2090), + SecurityTokenInvalidSignatureException => SR.GetResourceString(SR.ID2091), + + _ => SR.GetResourceString(SR.ID2004) + }, + uri: result.Exception switch + { + SecurityTokenInvalidIssuerException => SR.FormatID8000(SR.ID2088), + SecurityTokenInvalidTypeException => SR.FormatID8000(SR.ID2089), + SecurityTokenSignatureKeyNotFoundException => SR.FormatID8000(SR.ID2090), + SecurityTokenInvalidSignatureException => SR.FormatID8000(SR.ID2091), - context.Logger.LogTrace(SR.GetResourceString(SR.ID6001), context.Token, context.Principal.Claims); + _ => SR.FormatID8000(SR.ID2004) + }); return default; } + + // Attach the principal extracted from the token to the parent event context and store + // the token type (resolved from "typ" or "token_usage") as a special private claim. + context.Principal = new ClaimsPrincipal(result.ClaimsIdentity).SetTokenType(result.TokenType switch + { + null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)), + + // Both at+jwt and application/at+jwt are supported for access tokens. + JsonWebTokenTypes.AccessToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken + => TokenTypeHints.AccessToken, + + _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) + }); + + context.Logger.LogTrace(SR.GetResourceString(SR.ID6001), context.Token, context.Principal.Claims); + + return default; } + } + + /// + /// Contains the logic responsible of validating the tokens using OAuth 2.0 introspection. + /// + public class IntrospectToken : IOpenIddictValidationHandler + { + private readonly OpenIddictValidationService _service; + + public IntrospectToken(OpenIddictValidationService service) + => _service = service; /// - /// Contains the logic responsible of validating the tokens using OAuth 2.0 introspection. + /// Gets the default descriptor definition assigned to this handler. /// - public class IntrospectToken : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ValidateIdentityModelToken.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenContext context) { - private readonly OpenIddictValidationService _service; - - public IntrospectToken(OpenIddictValidationService service) - => _service = service; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseSingletonHandler() - .SetOrder(ValidateIdentityModelToken.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If a principal was already attached, don't overwrite it. - if (context.Principal is not null) - { - return; - } + // If a principal was already attached, don't overwrite it. + if (context.Principal is not null) + { + return; + } - Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010)); + Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010)); - var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); + var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0140)); - if (string.IsNullOrEmpty(configuration.IntrospectionEndpoint) || - !Uri.TryCreate(configuration.IntrospectionEndpoint, UriKind.Absolute, out Uri? address) || - !address.IsWellFormedOriginalString()) - { - context.Reject( - error: Errors.ServerError, - description: SR.GetResourceString(SR.ID2092), - uri: SR.FormatID8000(SR.ID2092)); + if (string.IsNullOrEmpty(configuration.IntrospectionEndpoint) || + !Uri.TryCreate(configuration.IntrospectionEndpoint, UriKind.Absolute, out Uri? address) || + !address.IsWellFormedOriginalString()) + { + context.Reject( + error: Errors.ServerError, + description: SR.GetResourceString(SR.ID2092), + uri: SR.FormatID8000(SR.ID2092)); - return; - } + return; + } - ClaimsPrincipal principal; + ClaimsPrincipal principal; - try + try + { + principal = await _service.IntrospectTokenAsync(address, context.Token, context.ValidTokenTypes.Count switch { - principal = await _service.IntrospectTokenAsync(address, context.Token, context.ValidTokenTypes.Count switch - { - // Infer the token type hint sent to the authorization server to help speed up - // the token resolution lookup. If multiple types are accepted, no hint is sent. - 1 => context.ValidTokenTypes.ElementAt(0), - _ => null - }) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0141)); - } + // Infer the token type hint sent to the authorization server to help speed up + // the token resolution lookup. If multiple types are accepted, no hint is sent. + 1 => context.ValidTokenTypes.ElementAt(0), + _ => null + }) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0141)); + } - catch (Exception exception) - { - context.Logger.LogDebug(exception, SR.GetResourceString(SR.ID6155)); + catch (Exception exception) + { + context.Logger.LogDebug(exception, SR.GetResourceString(SR.ID6155)); - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2004), - uri: SR.FormatID8000(SR.ID2004)); + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2004), + uri: SR.FormatID8000(SR.ID2004)); - return; - } + return; + } - // OpenIddict-based authorization servers always return the actual token type using - // the special "token_usage" claim, that helps resource servers determine whether the - // introspected token is one of the expected types and prevents token substitution attacks. - // - // If a "token_usage" claim can be extracted from the principal, use it to determine - // whether the token details returned by the authorization server correspond to a - // token whose type is considered acceptable based on the valid types collection. - // - // If the valid types collection is empty, all types of tokens are considered valid. - var usage = principal.GetClaim(Claims.TokenUsage); - if (!string.IsNullOrEmpty(usage) && context.ValidTokenTypes.Count > 0 && - !context.ValidTokenTypes.Contains(usage)) - { - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2110), - uri: SR.FormatID8000(SR.ID2110)); + // OpenIddict-based authorization servers always return the actual token type using + // the special "token_usage" claim, that helps resource servers determine whether the + // introspected token is one of the expected types and prevents token substitution attacks. + // + // If a "token_usage" claim can be extracted from the principal, use it to determine + // whether the token details returned by the authorization server correspond to a + // token whose type is considered acceptable based on the valid types collection. + // + // If the valid types collection is empty, all types of tokens are considered valid. + var usage = principal.GetClaim(Claims.TokenUsage); + if (!string.IsNullOrEmpty(usage) && context.ValidTokenTypes.Count > 0 && + !context.ValidTokenTypes.Contains(usage)) + { + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2110), + uri: SR.FormatID8000(SR.ID2110)); - return; - } + return; + } - // Note: at this point, the "token_usage" claim value is guaranteed to correspond - // to a known value as it is checked when validating the introspection response. - // - // If no value could be resolved, the token is assumed to be an access token. - context.Principal = principal.SetTokenType(usage ?? TokenTypeHints.AccessToken); + // Note: at this point, the "token_usage" claim value is guaranteed to correspond + // to a known value as it is checked when validating the introspection response. + // + // If no value could be resolved, the token is assumed to be an access token. + context.Principal = principal.SetTokenType(usage ?? TokenTypeHints.AccessToken); - context.Logger.LogTrace(SR.GetResourceString(SR.ID6154), context.Token, context.Principal.Claims); - } + context.Logger.LogTrace(SR.GetResourceString(SR.ID6154), context.Token, context.Principal.Claims); } + } + /// + /// Contains the logic responsible of normalizing the scope claims stored in the tokens. + /// + public class NormalizeScopeClaims : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of normalizing the scope claims stored in the tokens. + /// Gets the default descriptor definition assigned to this handler. /// - public class NormalizeScopeClaims : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(IntrospectToken.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(IntrospectToken.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.Principal is null) - { - return default; - } - - // Note: in previous OpenIddict versions, scopes were represented as a JSON array - // and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim - // is formatted as a unique space-separated string containing all the granted scopes. - // To ensure access tokens generated by previous versions are still correctly handled, - // both formats (unique space-separated string or multiple scope claims) must be supported. - // To achieve that, all the "scope" claims are combined into a single one containg all the values. - // Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. - var scopes = context.Principal.GetClaims(Claims.Scope); - if (scopes.Length > 1) - { - context.Principal.SetClaim(Claims.Scope, string.Join(" ", scopes)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + if (context.Principal is null) + { return default; } + + // Note: in previous OpenIddict versions, scopes were represented as a JSON array + // and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim + // is formatted as a unique space-separated string containing all the granted scopes. + // To ensure access tokens generated by previous versions are still correctly handled, + // both formats (unique space-separated string or multiple scope claims) must be supported. + // To achieve that, all the "scope" claims are combined into a single one containg all the values. + // Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. + var scopes = context.Principal.GetClaims(Claims.Scope); + if (scopes.Length > 1) + { + context.Principal.SetClaim(Claims.Scope, string.Join(" ", scopes)); + } + + return default; } + } + /// + /// Contains the logic responsible of mapping internal claims used by OpenIddict. + /// + public class MapInternalClaims : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of mapping internal claims used by OpenIddict. + /// Gets the default descriptor definition assigned to this handler. /// - public class MapInternalClaims : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(NormalizeScopeClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(NormalizeScopeClaims.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - if (context.Principal is null) - { - return default; - } + if (context.Principal is null) + { + return default; + } - // To reduce the size of tokens, some of the private claims used by OpenIddict - // are mapped to their standard equivalent before being removed from the token. - // This handler is responsible of adding back the private claims to the principal - // when receiving the token (e.g "oi_prst" is resolved from the "scope" claim). + // To reduce the size of tokens, some of the private claims used by OpenIddict + // are mapped to their standard equivalent before being removed from the token. + // This handler is responsible of adding back the private claims to the principal + // when receiving the token (e.g "oi_prst" is resolved from the "scope" claim). - // In OpenIddict 3.0, the creation date of a token is stored in "oi_crt_dt". - // If the claim doesn't exist, try to infer it from the standard "iat" JWT claim. - if (!context.Principal.HasClaim(Claims.Private.CreationDate)) + // In OpenIddict 3.0, the creation date of a token is stored in "oi_crt_dt". + // If the claim doesn't exist, try to infer it from the standard "iat" JWT claim. + if (!context.Principal.HasClaim(Claims.Private.CreationDate)) + { + var date = context.Principal.GetClaim(Claims.IssuedAt); + if (!string.IsNullOrEmpty(date) && + long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { - var date = context.Principal.GetClaim(Claims.IssuedAt); - if (!string.IsNullOrEmpty(date) && - long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) - { - context.Principal.SetCreationDate(DateTimeOffset.FromUnixTimeSeconds(value)); - } + context.Principal.SetCreationDate(DateTimeOffset.FromUnixTimeSeconds(value)); } + } - // In OpenIddict 3.0, the expiration date of a token is stored in "oi_exp_dt". - // If the claim doesn't exist, try to infer it from the standard "exp" JWT claim. - if (!context.Principal.HasClaim(Claims.Private.ExpirationDate)) + // In OpenIddict 3.0, the expiration date of a token is stored in "oi_exp_dt". + // If the claim doesn't exist, try to infer it from the standard "exp" JWT claim. + if (!context.Principal.HasClaim(Claims.Private.ExpirationDate)) + { + var date = context.Principal.GetClaim(Claims.ExpiresAt); + if (!string.IsNullOrEmpty(date) && + long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { - var date = context.Principal.GetClaim(Claims.ExpiresAt); - if (!string.IsNullOrEmpty(date) && - long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) - { - context.Principal.SetExpirationDate(DateTimeOffset.FromUnixTimeSeconds(value)); - } + context.Principal.SetExpirationDate(DateTimeOffset.FromUnixTimeSeconds(value)); } + } - // In OpenIddict 3.0, the audiences allowed to receive a token are stored in "oi_aud". - // If no such claim exists, try to infer them from the standard "aud" JWT claims. - if (!context.Principal.HasClaim(Claims.Private.Audience)) + // In OpenIddict 3.0, the audiences allowed to receive a token are stored in "oi_aud". + // If no such claim exists, try to infer them from the standard "aud" JWT claims. + if (!context.Principal.HasClaim(Claims.Private.Audience)) + { + var audiences = context.Principal.GetClaims(Claims.Audience); + if (audiences.Any()) { - var audiences = context.Principal.GetClaims(Claims.Audience); - if (audiences.Any()) - { - context.Principal.SetAudiences(audiences); - } + context.Principal.SetAudiences(audiences); } + } - // In OpenIddict 3.0, the presenters allowed to use a token are stored in "oi_prst". - // If no such claim exists, try to infer them from the standard "azp" and "client_id" JWT claims. - // - // Note: in previous OpenIddict versions, the presenters were represented in JWT tokens - // using the "azp" claim (defined by OpenID Connect), for which a single value could be - // specified. To ensure presenters stored in JWT tokens created by OpenIddict 1.x/2.x - // can still be read with OpenIddict 3.0, the presenter is automatically inferred from - // the "azp" or "client_id" claim if no "oi_prst" claim was found in the principal. - if (!context.Principal.HasClaim(Claims.Private.Presenter)) - { - var presenter = context.Principal.GetClaim(Claims.AuthorizedParty) ?? - context.Principal.GetClaim(Claims.ClientId); + // In OpenIddict 3.0, the presenters allowed to use a token are stored in "oi_prst". + // If no such claim exists, try to infer them from the standard "azp" and "client_id" JWT claims. + // + // Note: in previous OpenIddict versions, the presenters were represented in JWT tokens + // using the "azp" claim (defined by OpenID Connect), for which a single value could be + // specified. To ensure presenters stored in JWT tokens created by OpenIddict 1.x/2.x + // can still be read with OpenIddict 3.0, the presenter is automatically inferred from + // the "azp" or "client_id" claim if no "oi_prst" claim was found in the principal. + if (!context.Principal.HasClaim(Claims.Private.Presenter)) + { + var presenter = context.Principal.GetClaim(Claims.AuthorizedParty) ?? + context.Principal.GetClaim(Claims.ClientId); - if (!string.IsNullOrEmpty(presenter)) - { - context.Principal.SetPresenters(presenter); - } + if (!string.IsNullOrEmpty(presenter)) + { + context.Principal.SetPresenters(presenter); } + } - // In OpenIddict 3.0, the scopes granted to an application are stored in "oi_scp". - // If no such claim exists, try to infer them from the standard "scope" JWT claim, - // which is guaranteed to be a unique space-separated claim containing all the values. - if (!context.Principal.HasClaim(Claims.Private.Scope)) + // In OpenIddict 3.0, the scopes granted to an application are stored in "oi_scp". + // If no such claim exists, try to infer them from the standard "scope" JWT claim, + // which is guaranteed to be a unique space-separated claim containing all the values. + if (!context.Principal.HasClaim(Claims.Private.Scope)) + { + var scope = context.Principal.GetClaim(Claims.Scope); + if (!string.IsNullOrEmpty(scope)) { - var scope = context.Principal.GetClaim(Claims.Scope); - if (!string.IsNullOrEmpty(scope)) - { - context.Principal.SetScopes(scope.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)); - } + context.Principal.SetScopes(scope.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)); } - - return default; } - } - /// - /// Contains the logic responsible of restoring the properties associated with a reference token entry. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class RestoreReferenceTokenProperties : IOpenIddictValidationHandler - { - private readonly IOpenIddictTokenManager _tokenManager; + return default; + } + } - public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); + /// + /// Contains the logic responsible of restoring the properties associated with a reference token entry. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class RestoreReferenceTokenProperties : IOpenIddictValidationHandler + { + private readonly IOpenIddictTokenManager _tokenManager; - public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(MapInternalClaims.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); + public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; - /// - public async ValueTask HandleAsync(ValidateTokenContext context) + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(MapInternalClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenContext context) + { + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.Principal is null || string.IsNullOrEmpty(context.TokenId)) - { - return; - } + throw new ArgumentNullException(nameof(context)); + } - var token = await _tokenManager.FindByIdAsync(context.TokenId); - if (token is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); - } + if (context.Principal is null || string.IsNullOrEmpty(context.TokenId)) + { + return; + } - // Restore the creation/expiration dates/identifiers from the token entry metadata. - context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) - .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) - .SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) - .SetTokenId(await _tokenManager.GetIdAsync(token)) - .SetTokenType(await _tokenManager.GetTypeAsync(token)); + var token = await _tokenManager.FindByIdAsync(context.TokenId); + if (token is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); } + + // Restore the creation/expiration dates/identifiers from the token entry metadata. + context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) + .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) + .SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) + .SetTokenId(await _tokenManager.GetIdAsync(token)) + .SetTokenType(await _tokenManager.GetTypeAsync(token)); } + } + /// + /// Contains the logic responsible of rejecting authentication demands for which no valid principal was resolved. + /// + public class ValidatePrincipal : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of rejecting authentication demands for which no valid principal was resolved. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidatePrincipal : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.Principal is null) - { - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2004), - uri: SR.FormatID8000(SR.ID2004)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - return default; - } + if (context.Principal is null) + { + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2004), + uri: SR.FormatID8000(SR.ID2004)); - // When using JWT or Data Protection tokens, the correct token type is always enforced by IdentityModel - // (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings). - // To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used, - // which requires that a special claim containing the token type be present in the security principal. - var type = context.Principal.GetTokenType(); - if (string.IsNullOrEmpty(type)) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0004)); - } + return default; + } - if (context.ValidTokenTypes.Count > 0 && !context.ValidTokenTypes.Contains(type)) - { - throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes))); - } + // When using JWT or Data Protection tokens, the correct token type is always enforced by IdentityModel + // (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings). + // To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used, + // which requires that a special claim containing the token type be present in the security principal. + var type = context.Principal.GetTokenType(); + if (string.IsNullOrEmpty(type)) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0004)); + } - return default; + if (context.ValidTokenTypes.Count > 0 && !context.ValidTokenTypes.Contains(type)) + { + throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes))); } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authentication demands containing expired access tokens. + /// + public class ValidateExpirationDate : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of rejecting authentication demands containing expired access tokens. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateExpirationDate : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidatePrincipal.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidatePrincipal.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - var date = context.Principal.GetExpirationDate(); - if (date.HasValue && date.Value < DateTimeOffset.UtcNow) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6156)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2019), - uri: SR.FormatID8000(SR.ID2019)); + var date = context.Principal.GetExpirationDate(); + if (date.HasValue && date.Value < DateTimeOffset.UtcNow) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6156)); - return default; - } + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2019), + uri: SR.FormatID8000(SR.ID2019)); return default; } + + return default; } + } + /// + /// Contains the logic responsible of rejecting authentication demands containing + /// access tokens that were issued to be used by another audience/resource server. + /// + public class ValidateAudience : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of rejecting authentication demands containing - /// access tokens that were issued to be used by another audience/resource server. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAudience : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ValidateTokenContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ValidateTokenContext context) - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - // If no explicit audience has been configured, - // skip the default audience validation. - if (context.Options.Audiences.Count == 0) - { - return default; - } + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // If the access token doesn't have any audience attached, return an error. - var audiences = context.Principal.GetAudiences(); - if (audiences.IsDefaultOrEmpty) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6157)); + // If no explicit audience has been configured, + // skip the default audience validation. + if (context.Options.Audiences.Count == 0) + { + return default; + } - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2093), - uri: SR.FormatID8000(SR.ID2093)); + // If the access token doesn't have any audience attached, return an error. + var audiences = context.Principal.GetAudiences(); + if (audiences.IsDefaultOrEmpty) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6157)); - return default; - } + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2093), + uri: SR.FormatID8000(SR.ID2093)); - // If the access token doesn't include any registered audience, return an error. - if (!audiences.Intersect(context.Options.Audiences, StringComparer.Ordinal).Any()) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6158)); + return default; + } - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2094), - uri: SR.FormatID8000(SR.ID2094)); + // If the access token doesn't include any registered audience, return an error. + if (!audiences.Intersect(context.Options.Audiences, StringComparer.Ordinal).Any()) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6158)); - return default; - } + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2094), + uri: SR.FormatID8000(SR.ID2094)); return default; } + + return default; } + } + + /// + /// Contains the logic responsible of authentication demands a token whose + /// associated token entry is no longer valid (e.g was revoked). + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateTokenEntry : IOpenIddictValidationHandler + { + private readonly IOpenIddictTokenManager _tokenManager; + + public ValidateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); + + public ValidateTokenEntry(IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// - /// Contains the logic responsible of authentication demands a token whose - /// associated token entry is no longer valid (e.g was revoked). - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateTokenEntry : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateAudience.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenContext context) { - private readonly IOpenIddictTokenManager _tokenManager; - - public ValidateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } - public ValidateTokenEntry(IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateAudience.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); + var identifier = context.Principal.GetTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + return; + } - /// - public async ValueTask HandleAsync(ValidateTokenContext context) + var token = await _tokenManager.FindByIdAsync(identifier); + if (token is null || !await _tokenManager.HasStatusAsync(token, Statuses.Valid)) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2019), + uri: SR.FormatID8000(SR.ID2019)); - var identifier = context.Principal.GetTokenId(); - if (string.IsNullOrEmpty(identifier)) - { - return; - } + return; + } - var token = await _tokenManager.FindByIdAsync(identifier); - if (token is null || !await _tokenManager.HasStatusAsync(token, Statuses.Valid)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); + // Restore the creation/expiration dates/identifiers from the token entry metadata. + context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) + .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) + .SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) + .SetTokenId(await _tokenManager.GetIdAsync(token)) + .SetTokenType(await _tokenManager.GetTypeAsync(token)); + } + } - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2019), - uri: SR.FormatID8000(SR.ID2019)); + /// + /// Contains the logic responsible of authentication demands a token whose + /// associated authorization entry is no longer valid (e.g was revoked). + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class ValidateAuthorizationEntry : IOpenIddictValidationHandler + { + private readonly IOpenIddictAuthorizationManager _authorizationManager; - return; - } + public ValidateAuthorizationEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0142)); - // Restore the creation/expiration dates/identifiers from the token entry metadata. - context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) - .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) - .SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) - .SetTokenId(await _tokenManager.GetIdAsync(token)) - .SetTokenType(await _tokenManager.GetTypeAsync(token)); - } - } + public ValidateAuthorizationEntry(IOpenIddictAuthorizationManager authorizationManager) + => _authorizationManager = authorizationManager; /// - /// Contains the logic responsible of authentication demands a token whose - /// associated authorization entry is no longer valid (e.g was revoked). - /// Note: this handler is not used when the degraded mode is enabled. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAuthorizationEntry : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ValidateTokenContext context) { - private readonly IOpenIddictAuthorizationManager _authorizationManager; - - public ValidateAuthorizationEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0142)); - - public ValidateAuthorizationEntry(IOpenIddictAuthorizationManager authorizationManager) - => _authorizationManager = authorizationManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ValidateTokenContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - var identifier = context.Principal.GetAuthorizationId(); - if (string.IsNullOrEmpty(identifier)) - { - return; - } + var identifier = context.Principal.GetAuthorizationId(); + if (string.IsNullOrEmpty(identifier)) + { + return; + } - var authorization = await _authorizationManager.FindByIdAsync(identifier); - if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier); + var authorization = await _authorizationManager.FindByIdAsync(identifier); + if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier); - context.Reject( - error: Errors.InvalidToken, - description: SR.GetResourceString(SR.ID2023), - uri: SR.FormatID8000(SR.ID2023)); + context.Reject( + error: Errors.InvalidToken, + description: SR.GetResourceString(SR.ID2023), + uri: SR.FormatID8000(SR.ID2023)); - return; - } + return; } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs index 05de9d2b..7392b0d1 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs @@ -13,287 +13,286 @@ using static OpenIddict.Validation.OpenIddictValidationEvents; using static OpenIddict.Validation.OpenIddictValidationHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +[EditorBrowsable(EditorBrowsableState.Never)] +public static partial class OpenIddictValidationHandlers { - [EditorBrowsable(EditorBrowsableState.Never)] - public static partial class OpenIddictValidationHandlers + public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( + /* + * Authentication processing: + */ + EvaluateValidatedTokens.Descriptor, + ValidateAccessToken.Descriptor, + + /* + * Challenge processing: + */ + AttachDefaultChallengeError.Descriptor) + + .AddRange(Discovery.DefaultHandlers) + .AddRange(Introspection.DefaultHandlers) + .AddRange(Protection.DefaultHandlers); + + /// + /// Contains the logic responsible of selecting the token types that should be validated. + /// + public class EvaluateValidatedTokens : IOpenIddictValidationHandler { - public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( - /* - * Authentication processing: - */ - EvaluateValidatedTokens.Descriptor, - ValidateAccessToken.Descriptor, - - /* - * Challenge processing: - */ - AttachDefaultChallengeError.Descriptor) - - .AddRange(Discovery.DefaultHandlers) - .AddRange(Introspection.DefaultHandlers) - .AddRange(Protection.DefaultHandlers); - /// - /// Contains the logic responsible of selecting the token types that should be validated. + /// Gets the default descriptor definition assigned to this handler. /// - public class EvaluateValidatedTokens : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - (context.ValidateAccessToken, context.RequireAccessToken) = context.EndpointType switch - { - // The validation handler is responsible of validating access tokens for endpoints - // it doesn't manage (typically, API endpoints using token authentication). - // - // As such, sending an access token is not mandatory: API endpoints that require - // authentication can set up an authorization policy to reject such requests later - // in the request processing pipeline (typically, via the authorization middleware). - OpenIddictValidationEndpointType.Unknown => (true, false), - - _ => (false, false) - }; - - // Note: unlike the equivalent event in the server stack, authentication can be triggered for - // arbitrary requests (typically, API endpoints that are not owned by the validation stack). - // As such, the token is not directly resolved from the request, that may be null at this stage. - // Instead, the token is expected to be populated by one or multiple handlers provided by the host. - - return default; + throw new ArgumentNullException(nameof(context)); } + + (context.ValidateAccessToken, context.RequireAccessToken) = context.EndpointType switch + { + // The validation handler is responsible of validating access tokens for endpoints + // it doesn't manage (typically, API endpoints using token authentication). + // + // As such, sending an access token is not mandatory: API endpoints that require + // authentication can set up an authorization policy to reject such requests later + // in the request processing pipeline (typically, via the authorization middleware). + OpenIddictValidationEndpointType.Unknown => (true, false), + + _ => (false, false) + }; + + // Note: unlike the equivalent event in the server stack, authentication can be triggered for + // arbitrary requests (typically, API endpoints that are not owned by the validation stack). + // As such, the token is not directly resolved from the request, that may be null at this stage. + // Instead, the token is expected to be populated by one or multiple handlers provided by the host. + + return default; } + } + + /// + /// Contains the logic responsible of ensuring a token was correctly resolved from the context. + /// + public class ValidateAccessToken : IOpenIddictValidationHandler + { + private readonly IOpenIddictValidationDispatcher _dispatcher; + + public ValidateAccessToken(IOpenIddictValidationDispatcher dispatcher) + => _dispatcher = dispatcher; /// - /// Contains the logic responsible of ensuring a token was correctly resolved from the context. + /// Gets the default descriptor definition assigned to this handler. /// - public class ValidateAccessToken : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public async ValueTask HandleAsync(ProcessAuthenticationContext context) { - private readonly IOpenIddictValidationDispatcher _dispatcher; - - public ValidateAccessToken(IOpenIddictValidationDispatcher dispatcher) - => _dispatcher = dispatcher; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() - .UseScopedHandler() - .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public async ValueTask HandleAsync(ProcessAuthenticationContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - if (context.AccessTokenPrincipal is not null) - { - return; - } + if (context.AccessTokenPrincipal is not null) + { + return; + } - if (string.IsNullOrEmpty(context.AccessToken)) + if (string.IsNullOrEmpty(context.AccessToken)) + { + if (context.RequireAccessToken) { - if (context.RequireAccessToken) - { - context.Reject( - error: Errors.MissingToken, - description: SR.GetResourceString(SR.ID2000), - uri: SR.FormatID8000(SR.ID2000)); - - return; - } + context.Reject( + error: Errors.MissingToken, + description: SR.GetResourceString(SR.ID2000), + uri: SR.FormatID8000(SR.ID2000)); return; } - var notification = new ValidateTokenContext(context.Transaction) - { - Token = context.AccessToken, - ValidTokenTypes = { TokenTypeHints.AccessToken } - }; + return; + } - await _dispatcher.DispatchAsync(notification); + var notification = new ValidateTokenContext(context.Transaction) + { + Token = context.AccessToken, + ValidTokenTypes = { TokenTypeHints.AccessToken } + }; - if (notification.IsRequestHandled) - { - context.HandleRequest(); - return; - } + await _dispatcher.DispatchAsync(notification); - else if (notification.IsRequestSkipped) - { - context.SkipRequest(); - return; - } + if (notification.IsRequestHandled) + { + context.HandleRequest(); + return; + } - else if (notification.IsRejected) - { - context.Reject( - error: notification.Error ?? Errors.InvalidRequest, - description: notification.ErrorDescription, - uri: notification.ErrorUri); - return; - } + else if (notification.IsRequestSkipped) + { + context.SkipRequest(); + return; + } - context.AccessTokenPrincipal = notification.Principal; + else if (notification.IsRejected) + { + context.Reject( + error: notification.Error ?? Errors.InvalidRequest, + description: notification.ErrorDescription, + uri: notification.ErrorUri); + return; } + + context.AccessTokenPrincipal = notification.Principal; } + } + /// + /// Contains the logic responsible of ensuring that the challenge response contains an appropriate error. + /// + public class AttachDefaultChallengeError : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of ensuring that the challenge response contains an appropriate error. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachDefaultChallengeError : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessChallengeContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessChallengeContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - // If an error was explicitly set by the application, don't override it. - if (!string.IsNullOrEmpty(context.Response.Error) || - !string.IsNullOrEmpty(context.Response.ErrorDescription) || - !string.IsNullOrEmpty(context.Response.ErrorUri)) - { - return default; - } + throw new ArgumentNullException(nameof(context)); + } - // Try to retrieve the authentication context from the validation transaction and use - // the error details returned during the authentication processing, if available. - // If no error is attached to the authentication context, this likely means that - // the request was rejected very early without even checking the access token or was - // rejected due to a lack of permission. In this case, return an insufficient_access error - // to inform the client that the user is not allowed to perform the requested action. + // If an error was explicitly set by the application, don't override it. + if (!string.IsNullOrEmpty(context.Response.Error) || + !string.IsNullOrEmpty(context.Response.ErrorDescription) || + !string.IsNullOrEmpty(context.Response.ErrorUri)) + { + return default; + } - var notification = context.Transaction.GetProperty( - typeof(ProcessAuthenticationContext).FullName!); + // Try to retrieve the authentication context from the validation transaction and use + // the error details returned during the authentication processing, if available. + // If no error is attached to the authentication context, this likely means that + // the request was rejected very early without even checking the access token or was + // rejected due to a lack of permission. In this case, return an insufficient_access error + // to inform the client that the user is not allowed to perform the requested action. - if (!string.IsNullOrEmpty(notification?.Error)) - { - context.Response.Error = notification.Error; - context.Response.ErrorDescription = notification.ErrorDescription; - context.Response.ErrorUri = notification.ErrorUri; - } + var notification = context.Transaction.GetProperty( + typeof(ProcessAuthenticationContext).FullName!); - else - { - context.Response.Error = Errors.InsufficientAccess; - context.Response.ErrorDescription = SR.GetResourceString(SR.ID2095); - context.Response.ErrorUri = SR.FormatID8000(SR.ID2095); - } + if (!string.IsNullOrEmpty(notification?.Error)) + { + context.Response.Error = notification.Error; + context.Response.ErrorDescription = notification.ErrorDescription; + context.Response.ErrorUri = notification.ErrorUri; + } - return default; + else + { + context.Response.Error = Errors.InsufficientAccess; + context.Response.ErrorDescription = SR.GetResourceString(SR.ID2095); + context.Response.ErrorUri = SR.FormatID8000(SR.ID2095); } + + return default; } + } + /// + /// Contains the logic responsible of attaching the appropriate parameters to the error response. + /// + public class AttachErrorParameters : IOpenIddictValidationHandler + { /// - /// Contains the logic responsible of attaching the appropriate parameters to the error response. + /// Gets the default descriptor definition assigned to this handler. /// - public class AttachErrorParameters : IOpenIddictValidationHandler + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessErrorContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(ProcessErrorContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } + throw new ArgumentNullException(nameof(context)); + } - context.Response.Error = context.Error; - context.Response.ErrorDescription = context.ErrorDescription; - context.Response.ErrorUri = context.ErrorUri; + context.Response.Error = context.Error; + context.Response.ErrorDescription = context.ErrorDescription; + context.Response.ErrorUri = context.ErrorUri; - if (context.Parameters.Count > 0) + if (context.Parameters.Count > 0) + { + foreach (var parameter in context.Parameters) { - foreach (var parameter in context.Parameters) - { - context.Response.SetParameter(parameter.Key, parameter.Value); - } + context.Response.SetParameter(parameter.Key, parameter.Value); } - - return default; } + + return default; } + } + /// + /// Contains the logic responsible of extracting potential errors from the response. + /// + public class HandleErrorResponse : IOpenIddictValidationHandler where TContext : BaseValidatingContext + { /// - /// Contains the logic responsible of extracting potential errors from the response. + /// Gets the default descriptor definition assigned to this handler. /// - public class HandleErrorResponse : IOpenIddictValidationHandler where TContext : BaseValidatingContext + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .UseSingletonHandler>() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictValidationHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(TContext context) { - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictValidationHandlerDescriptor Descriptor { get; } - = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .UseSingletonHandler>() - .SetOrder(int.MinValue + 100_000) - .SetType(OpenIddictValidationHandlerType.BuiltIn) - .Build(); - - /// - public ValueTask HandleAsync(TContext context) + if (context is null) { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (!string.IsNullOrEmpty(context.Transaction.Response?.Error)) - { - context.Reject( - error: context.Transaction.Response.Error, - description: context.Transaction.Response.ErrorDescription, - uri: context.Transaction.Response.ErrorUri); + throw new ArgumentNullException(nameof(context)); + } - return default; - } + if (!string.IsNullOrEmpty(context.Transaction.Response?.Error)) + { + context.Reject( + error: context.Transaction.Response.Error, + description: context.Transaction.Response.ErrorDescription, + uri: context.Transaction.Response.ErrorUri); return default; } + + return default; } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHelpers.cs b/src/OpenIddict.Validation/OpenIddictValidationHelpers.cs index fb6d34e7..cb3e123d 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHelpers.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHelpers.cs @@ -7,74 +7,73 @@ using System; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Exposes extensions simplifying the integration with the OpenIddict validation services. +/// +public static class OpenIddictValidationHelpers { /// - /// Exposes extensions simplifying the integration with the OpenIddict validation services. + /// Retrieves a property value from the validation transaction using the specified name. /// - public static class OpenIddictValidationHelpers + /// The type of the property. + /// The validation transaction. + /// The property name. + /// The property value or null if it couldn't be found. + public static TProperty? GetProperty( + this OpenIddictValidationTransaction transaction, string name) where TProperty : class { - /// - /// Retrieves a property value from the validation transaction using the specified name. - /// - /// The type of the property. - /// The validation transaction. - /// The property name. - /// The property value or null if it couldn't be found. - public static TProperty? GetProperty( - this OpenIddictValidationTransaction transaction, string name) where TProperty : class + if (transaction is null) { - if (transaction is null) - { - throw new ArgumentNullException(nameof(transaction)); - } - - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0106), nameof(name)); - } - - if (transaction.Properties.TryGetValue(name, out var property) && property is TProperty result) - { - return result; - } + throw new ArgumentNullException(nameof(transaction)); + } - return null; + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0106), nameof(name)); } - /// - /// Sets a property in the validation transaction using the specified name and value. - /// - /// The type of the property. - /// The validation transaction. - /// The property name. - /// The property value. - /// The validation transaction, so that calls can be easily chained. - public static OpenIddictValidationTransaction SetProperty( - this OpenIddictValidationTransaction transaction, - string name, TProperty? value) where TProperty : class + if (transaction.Properties.TryGetValue(name, out var property) && property is TProperty result) { - if (transaction is null) - { - throw new ArgumentNullException(nameof(transaction)); - } + return result; + } - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0106), nameof(name)); - } + return null; + } - if (value is null) - { - transaction.Properties.Remove(name); - } + /// + /// Sets a property in the validation transaction using the specified name and value. + /// + /// The type of the property. + /// The validation transaction. + /// The property name. + /// The property value. + /// The validation transaction, so that calls can be easily chained. + public static OpenIddictValidationTransaction SetProperty( + this OpenIddictValidationTransaction transaction, + string name, TProperty? value) where TProperty : class + { + if (transaction is null) + { + throw new ArgumentNullException(nameof(transaction)); + } - else - { - transaction.Properties[name] = value; - } + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0106), nameof(name)); + } - return transaction; + if (value is null) + { + transaction.Properties.Remove(name); } + + else + { + transaction.Properties[name] = value; + } + + return transaction; } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationOptions.cs b/src/OpenIddict.Validation/OpenIddictValidationOptions.cs index 487b1800..e2e77494 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationOptions.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationOptions.cs @@ -14,154 +14,153 @@ using Microsoft.IdentityModel.Tokens; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Provides various settings needed to configure the OpenIddict validation handler. +/// +public class OpenIddictValidationOptions { /// - /// Provides various settings needed to configure the OpenIddict validation handler. + /// Gets the list of encryption credentials used by the OpenIddict validation services. + /// Note: the encryption credentials are not used to protect/unprotect tokens issued + /// by ASP.NET Core Data Protection, that uses its own key ring, configured separately. + /// + /// + /// Note: OpenIddict automatically sorts the credentials based on the following algorithm: + /// + /// Symmetric keys are always preferred when they can be used for the operation (e.g token encryption). + /// X.509 keys are always preferred to non-X.509 asymmetric keys. + /// X.509 keys with the furthest expiration date are preferred. + /// X.509 keys whose backing certificate is not yet valid are never preferred. + /// + /// + public List EncryptionCredentials { get; } = new(); + + /// + /// Gets or sets the JWT handler used to protect and unprotect tokens. /// - public class OpenIddictValidationOptions + public JsonWebTokenHandler JsonWebTokenHandler { get; set; } = new() { - /// - /// Gets the list of encryption credentials used by the OpenIddict validation services. - /// Note: the encryption credentials are not used to protect/unprotect tokens issued - /// by ASP.NET Core Data Protection, that uses its own key ring, configured separately. - /// - /// - /// Note: OpenIddict automatically sorts the credentials based on the following algorithm: - /// - /// Symmetric keys are always preferred when they can be used for the operation (e.g token encryption). - /// X.509 keys are always preferred to non-X.509 asymmetric keys. - /// X.509 keys with the furthest expiration date are preferred. - /// X.509 keys whose backing certificate is not yet valid are never preferred. - /// - /// - public List EncryptionCredentials { get; } = new(); - - /// - /// Gets or sets the JWT handler used to protect and unprotect tokens. - /// - public JsonWebTokenHandler JsonWebTokenHandler { get; set; } = new() - { - SetDefaultTimesOnTokenCreation = false - }; - - /// - /// Gets the list of the handlers responsible of processing the OpenIddict validation operations. - /// Note: the list is automatically sorted based on the order assigned to each handler descriptor. - /// As such, it MUST NOT be mutated after options initialization to preserve the exact order. - /// - public List Handlers { get; } = new(OpenIddictValidationHandlers.DefaultHandlers); - - /// - /// Gets or sets the type of validation used by the OpenIddict validation services. - /// By default, local validation is always used. - /// - public OpenIddictValidationType ValidationType { get; set; } = OpenIddictValidationType.Direct; - - /// - /// Gets or sets the client identifier sent to the authorization server when using remote validation. - /// - public string? ClientId { get; set; } - - /// - /// Gets or sets the client secret sent to the authorization server when using remote validation. - /// - public string? ClientSecret { get; set; } - - /// - /// Gets or sets a boolean indicating whether a database call is made - /// to validate the authorization entry associated with the received tokens. - /// Note: enabling this option may have an impact on performance and - /// can only be used with an OpenIddict-based authorization server. - /// - public bool EnableAuthorizationEntryValidation { get; set; } - - /// - /// Gets or sets a boolean indicating whether a database call is made - /// to validate the token entry associated with the received tokens. - /// Note: enabling this option may have an impact on performance but - /// is required when the OpenIddict server emits reference tokens. - /// - public bool EnableTokenEntryValidation { get; set; } - - /// - /// Gets or sets the absolute URL of the OAuth 2.0/OpenID Connect server. - /// - public Uri? Issuer { get; set; } - - /// - /// Gets or sets the URL of the OAuth 2.0/OpenID Connect server discovery endpoint. - /// When the URL is relative, must be set and absolute. - /// - public Uri? MetadataAddress { get; set; } - - /// - /// Gets or sets the OAuth 2.0/OpenID Connect static server configuration, if applicable. - /// - public OpenIdConnectConfiguration? Configuration { get; set; } - - /// - /// Gets or sets the configuration manager used to retrieve - /// and cache the OAuth 2.0/OpenID Connect server configuration. - /// - public IConfigurationManager ConfigurationManager { get; set; } = default!; - - /// - /// Gets the intended audiences of this resource server. - /// Setting this property is recommended when the authorization - /// server issues access tokens for multiple distinct resource servers. - /// - public HashSet Audiences { get; } = new(StringComparer.Ordinal); - - /// - /// Gets the token validation parameters used by the OpenIddict validation services. - /// - public TokenValidationParameters TokenValidationParameters { get; } = new() + SetDefaultTimesOnTokenCreation = false + }; + + /// + /// Gets the list of the handlers responsible of processing the OpenIddict validation operations. + /// Note: the list is automatically sorted based on the order assigned to each handler descriptor. + /// As such, it MUST NOT be mutated after options initialization to preserve the exact order. + /// + public List Handlers { get; } = new(OpenIddictValidationHandlers.DefaultHandlers); + + /// + /// Gets or sets the type of validation used by the OpenIddict validation services. + /// By default, local validation is always used. + /// + public OpenIddictValidationType ValidationType { get; set; } = OpenIddictValidationType.Direct; + + /// + /// Gets or sets the client identifier sent to the authorization server when using remote validation. + /// + public string? ClientId { get; set; } + + /// + /// Gets or sets the client secret sent to the authorization server when using remote validation. + /// + public string? ClientSecret { get; set; } + + /// + /// Gets or sets a boolean indicating whether a database call is made + /// to validate the authorization entry associated with the received tokens. + /// Note: enabling this option may have an impact on performance and + /// can only be used with an OpenIddict-based authorization server. + /// + public bool EnableAuthorizationEntryValidation { get; set; } + + /// + /// Gets or sets a boolean indicating whether a database call is made + /// to validate the token entry associated with the received tokens. + /// Note: enabling this option may have an impact on performance but + /// is required when the OpenIddict server emits reference tokens. + /// + public bool EnableTokenEntryValidation { get; set; } + + /// + /// Gets or sets the absolute URL of the OAuth 2.0/OpenID Connect server. + /// + public Uri? Issuer { get; set; } + + /// + /// Gets or sets the URL of the OAuth 2.0/OpenID Connect server discovery endpoint. + /// When the URL is relative, must be set and absolute. + /// + public Uri? MetadataAddress { get; set; } + + /// + /// Gets or sets the OAuth 2.0/OpenID Connect static server configuration, if applicable. + /// + public OpenIdConnectConfiguration? Configuration { get; set; } + + /// + /// Gets or sets the configuration manager used to retrieve + /// and cache the OAuth 2.0/OpenID Connect server configuration. + /// + public IConfigurationManager ConfigurationManager { get; set; } = default!; + + /// + /// Gets the intended audiences of this resource server. + /// Setting this property is recommended when the authorization + /// server issues access tokens for multiple distinct resource servers. + /// + public HashSet Audiences { get; } = new(StringComparer.Ordinal); + + /// + /// Gets the token validation parameters used by the OpenIddict validation services. + /// + public TokenValidationParameters TokenValidationParameters { get; } = new() + { + AuthenticationType = TokenValidationParameters.DefaultAuthenticationType, + ClockSkew = TimeSpan.Zero, + NameClaimType = Claims.Name, + RoleClaimType = Claims.Role, + // In previous versions of OpenIddict (1.x and 2.x), all the JWT tokens (access and identity tokens) + // were issued with the generic "typ": "JWT" header. To prevent confused deputy and token substitution + // attacks, a special "token_usage" claim was added to the JWT payload to convey the actual token type. + // This validator overrides the default logic used by IdentityModel to resolve the type from this claim. + TypeValidator = (type, token, parameters) => { - AuthenticationType = TokenValidationParameters.DefaultAuthenticationType, - ClockSkew = TimeSpan.Zero, - NameClaimType = Claims.Name, - RoleClaimType = Claims.Role, - // In previous versions of OpenIddict (1.x and 2.x), all the JWT tokens (access and identity tokens) - // were issued with the generic "typ": "JWT" header. To prevent confused deputy and token substitution - // attacks, a special "token_usage" claim was added to the JWT payload to convey the actual token type. - // This validator overrides the default logic used by IdentityModel to resolve the type from this claim. - TypeValidator = (type, token, parameters) => + // If available, try to resolve the actual type from the "token_usage" claim. + if (((JsonWebToken) token).TryGetPayloadValue(Claims.TokenUsage, out string usage)) { - // If available, try to resolve the actual type from the "token_usage" claim. - if (((JsonWebToken) token).TryGetPayloadValue(Claims.TokenUsage, out string usage)) + type = usage switch { - type = usage switch - { - TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken, - TokenTypeHints.IdToken => JsonWebTokenTypes.IdentityToken, - - _ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269)) - }; - } - - // At this point, throw an exception if the type cannot be resolved from the "typ" header - // (provided via the type delegate parameter) or inferred from the token_usage claim. - if (string.IsNullOrEmpty(type)) - { - throw new SecurityTokenInvalidTypeException(SR.GetResourceString(SR.ID0270)); - } + TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken, + TokenTypeHints.IdToken => JsonWebTokenTypes.IdentityToken, + + _ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269)) + }; + } - // Note: unlike IdentityModel, this custom validator deliberately uses case-insensitive comparisons. - if (parameters.ValidTypes is not null && parameters.ValidTypes.Any() && - !parameters.ValidTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) + // At this point, throw an exception if the type cannot be resolved from the "typ" header + // (provided via the type delegate parameter) or inferred from the token_usage claim. + if (string.IsNullOrEmpty(type)) + { + throw new SecurityTokenInvalidTypeException(SR.GetResourceString(SR.ID0270)); + } + + // Note: unlike IdentityModel, this custom validator deliberately uses case-insensitive comparisons. + if (parameters.ValidTypes is not null && parameters.ValidTypes.Any() && + !parameters.ValidTypes.Contains(type, StringComparer.OrdinalIgnoreCase)) + { + throw new SecurityTokenInvalidTypeException(SR.GetResourceString(SR.ID0271)) { - throw new SecurityTokenInvalidTypeException(SR.GetResourceString(SR.ID0271)) - { - InvalidType = type - }; - } - - return type; - }, - // Note: audience and lifetime are manually validated by OpenIddict itself. - ValidateAudience = false, - ValidateLifetime = false - }; - } + InvalidType = type + }; + } + + return type; + }, + // Note: audience and lifetime are manually validated by OpenIddict itself. + ValidateAudience = false, + ValidateLifetime = false + }; } diff --git a/src/OpenIddict.Validation/OpenIddictValidationRetriever.cs b/src/OpenIddict.Validation/OpenIddictValidationRetriever.cs index 2e191f62..eb832a0e 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationRetriever.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationRetriever.cs @@ -11,58 +11,57 @@ using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public class OpenIddictValidationRetriever : IConfigurationRetriever { - public class OpenIddictValidationRetriever : IConfigurationRetriever - { - private readonly OpenIddictValidationService _service; + private readonly OpenIddictValidationService _service; - /// - /// Creates a new instance of the class. - /// - /// The validation service. - public OpenIddictValidationRetriever(OpenIddictValidationService service) - => _service = service; + /// + /// Creates a new instance of the class. + /// + /// The validation service. + public OpenIddictValidationRetriever(OpenIddictValidationService service) + => _service = service; - /// - /// Retrieves the OpenID Connect server configuration from the specified address. - /// - /// The address of the remote metadata endpoint. - /// The retriever used by IdentityModel. - /// The that can be used to abort the operation. - /// The OpenID Connect server configuration retrieved from the remote server. - async Task IConfigurationRetriever.GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel) + /// + /// Retrieves the OpenID Connect server configuration from the specified address. + /// + /// The address of the remote metadata endpoint. + /// The retriever used by IdentityModel. + /// The that can be used to abort the operation. + /// The OpenID Connect server configuration retrieved from the remote server. + async Task IConfigurationRetriever.GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel) + { + if (string.IsNullOrEmpty(address)) { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); - } - - if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address)); - } + throw new ArgumentException(SR.GetResourceString(SR.ID0143), nameof(address)); + } - cancel.ThrowIfCancellationRequested(); + if (!Uri.TryCreate(address, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString()) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address)); + } - var configuration = await _service.GetConfigurationAsync(uri, cancel) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0145)); + cancel.ThrowIfCancellationRequested(); - if (!Uri.TryCreate(configuration.JwksUri, UriKind.Absolute, out uri) || !uri.IsWellFormedOriginalString()) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0146)); - } + var configuration = await _service.GetConfigurationAsync(uri, cancel) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0145)); - configuration.JsonWebKeySet = await _service.GetSecurityKeysAsync(uri, cancel) ?? - throw new InvalidOperationException(SR.GetResourceString(SR.ID0147)); + if (!Uri.TryCreate(configuration.JwksUri, UriKind.Absolute, out uri) || !uri.IsWellFormedOriginalString()) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0146)); + } - // Copy the signing keys found in the JSON Web Key Set to the SigningKeys collection. - foreach (var key in configuration.JsonWebKeySet.GetSigningKeys()) - { - configuration.SigningKeys.Add(key); - } + configuration.JsonWebKeySet = await _service.GetSecurityKeysAsync(uri, cancel) ?? + throw new InvalidOperationException(SR.GetResourceString(SR.ID0147)); - return configuration; + // Copy the signing keys found in the JSON Web Key Set to the SigningKeys collection. + foreach (var key in configuration.JsonWebKeySet.GetSigningKeys()) + { + configuration.SigningKeys.Add(key); } + + return configuration; } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationService.cs b/src/OpenIddict.Validation/OpenIddictValidationService.cs index 4dada6d8..f706fea1 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationService.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationService.cs @@ -17,525 +17,524 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Validation.OpenIddictValidationEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +public class OpenIddictValidationService { - public class OpenIddictValidationService + private readonly IServiceProvider _provider; + + /// + /// Creates a new instance of the class. + /// + /// The service provider. + public OpenIddictValidationService(IServiceProvider provider) + => _provider = provider; + + /// + /// Retrieves the OpenID Connect server configuration from the specified address. + /// + /// The address of the remote metadata endpoint. + /// The that can be used to abort the operation. + /// The OpenID Connect server configuration retrieved from the remote server. + public async ValueTask GetConfigurationAsync(Uri address, CancellationToken cancellationToken = default) { - private readonly IServiceProvider _provider; - - /// - /// Creates a new instance of the class. - /// - /// The service provider. - public OpenIddictValidationService(IServiceProvider provider) - => _provider = provider; - - /// - /// Retrieves the OpenID Connect server configuration from the specified address. - /// - /// The address of the remote metadata endpoint. - /// The that can be used to abort the operation. - /// The OpenID Connect server configuration retrieved from the remote server. - public async ValueTask GetConfigurationAsync(Uri address, CancellationToken cancellationToken = default) + if (address is null) { - if (address is null) - { - throw new ArgumentNullException(nameof(address)); - } + throw new ArgumentNullException(nameof(address)); + } - if (!address.IsAbsoluteUri) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address)); - } + if (!address.IsAbsoluteUri) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + // Note: this service is registered as a singleton service. As such, it cannot + // directly depend on scoped services like the validation provider. To work around + // this limitation, a scope is manually created for each method to this service. + var scope = _provider.CreateScope(); - cancellationToken.ThrowIfCancellationRequested(); + // Note: a try/finally block is deliberately used here to ensure the service scope + // can be disposed of asynchronously if it implements IAsyncDisposable. + try + { + var dispatcher = scope.ServiceProvider.GetRequiredService(); + var factory = scope.ServiceProvider.GetRequiredService(); + var transaction = await factory.CreateTransactionAsync(); - // Note: this service is registered as a singleton service. As such, it cannot - // directly depend on scoped services like the validation provider. To work around - // this limitation, a scope is manually created for each method to this service. - var scope = _provider.CreateScope(); + var request = new OpenIddictRequest(); + request = await PrepareConfigurationRequestAsync(); + request = await ApplyConfigurationRequestAsync(); + var response = await ExtractConfigurationResponseAsync(); - // Note: a try/finally block is deliberately used here to ensure the service scope - // can be disposed of asynchronously if it implements IAsyncDisposable. - try + var configuration = await HandleConfigurationResponseAsync(); + if (configuration is null) { - var dispatcher = scope.ServiceProvider.GetRequiredService(); - var factory = scope.ServiceProvider.GetRequiredService(); - var transaction = await factory.CreateTransactionAsync(); + throw new InvalidOperationException(SR.GetResourceString(SR.ID0145)); + } - var request = new OpenIddictRequest(); - request = await PrepareConfigurationRequestAsync(); - request = await ApplyConfigurationRequestAsync(); - var response = await ExtractConfigurationResponseAsync(); + return configuration; - var configuration = await HandleConfigurationResponseAsync(); - if (configuration is null) + async ValueTask PrepareConfigurationRequestAsync() + { + var context = new PrepareConfigurationRequestContext(transaction) { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0145)); - } + Address = address, + Request = request + }; - return configuration; + await dispatcher.DispatchAsync(context); - async ValueTask PrepareConfigurationRequestAsync() + if (context.IsRejected) { - var context = new PrepareConfigurationRequestContext(transaction) - { - Address = address, - Request = request - }; - - await dispatcher.DispatchAsync(context); - - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0148(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } - - return context.Request; + throw new OpenIddictExceptions.GenericException( + SR.FormatID0148(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); } - async ValueTask ApplyConfigurationRequestAsync() - { - var context = new ApplyConfigurationRequestContext(transaction) - { - Request = request - }; - - await dispatcher.DispatchAsync(context); + return context.Request; + } - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0149(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } + async ValueTask ApplyConfigurationRequestAsync() + { + var context = new ApplyConfigurationRequestContext(transaction) + { + Request = request + }; - return context.Request; - } + await dispatcher.DispatchAsync(context); - async ValueTask ExtractConfigurationResponseAsync() + if (context.IsRejected) { - var context = new ExtractConfigurationResponseContext(transaction) - { - Request = request - }; - - await dispatcher.DispatchAsync(context); + throw new OpenIddictExceptions.GenericException( + SR.FormatID0149(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); + } - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0150(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } + return context.Request; + } - Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007)); + async ValueTask ExtractConfigurationResponseAsync() + { + var context = new ExtractConfigurationResponseContext(transaction) + { + Request = request + }; - return context.Response; - } + await dispatcher.DispatchAsync(context); - async ValueTask HandleConfigurationResponseAsync() + if (context.IsRejected) { - var context = new HandleConfigurationResponseContext(transaction) - { - Request = request, - Response = response - }; - - await dispatcher.DispatchAsync(context); - - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0151(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } - - return context.Configuration; + throw new OpenIddictExceptions.GenericException( + SR.FormatID0150(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); } + + Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007)); + + return context.Response; } - finally + async ValueTask HandleConfigurationResponseAsync() { - if (scope is IAsyncDisposable disposable) + var context = new HandleConfigurationResponseContext(transaction) { - await disposable.DisposeAsync(); - } + Request = request, + Response = response + }; + + await dispatcher.DispatchAsync(context); - else + if (context.IsRejected) { - scope.Dispose(); + throw new OpenIddictExceptions.GenericException( + SR.FormatID0151(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); } + + return context.Configuration; } } - /// - /// Retrieves the security keys exposed by the specified JWKS endpoint. - /// - /// The address of the remote metadata endpoint. - /// The that can be used to abort the operation. - /// The security keys retrieved from the remote server. - public async ValueTask GetSecurityKeysAsync(Uri address, CancellationToken cancellationToken = default) + finally { - if (address is null) + if (scope is IAsyncDisposable disposable) { - throw new ArgumentNullException(nameof(address)); + await disposable.DisposeAsync(); } - if (!address.IsAbsoluteUri) + else { - throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address)); + scope.Dispose(); } + } + } - cancellationToken.ThrowIfCancellationRequested(); + /// + /// Retrieves the security keys exposed by the specified JWKS endpoint. + /// + /// The address of the remote metadata endpoint. + /// The that can be used to abort the operation. + /// The security keys retrieved from the remote server. + public async ValueTask GetSecurityKeysAsync(Uri address, CancellationToken cancellationToken = default) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } - // Note: this service is registered as a singleton service. As such, it cannot - // directly depend on scoped services like the validation provider. To work around - // this limitation, a scope is manually created for each method to this service. - var scope = _provider.CreateScope(); + if (!address.IsAbsoluteUri) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address)); + } - // Note: a try/finally block is deliberately used here to ensure the service scope - // can be disposed of asynchronously if it implements IAsyncDisposable. - try - { - var dispatcher = scope.ServiceProvider.GetRequiredService(); - var factory = scope.ServiceProvider.GetRequiredService(); - var transaction = await factory.CreateTransactionAsync(); + cancellationToken.ThrowIfCancellationRequested(); - var request = new OpenIddictRequest(); - request = await PrepareCryptographyRequestAsync(); - request = await ApplyCryptographyRequestAsync(); + // Note: this service is registered as a singleton service. As such, it cannot + // directly depend on scoped services like the validation provider. To work around + // this limitation, a scope is manually created for each method to this service. + var scope = _provider.CreateScope(); - var response = await ExtractCryptographyResponseAsync(); + // Note: a try/finally block is deliberately used here to ensure the service scope + // can be disposed of asynchronously if it implements IAsyncDisposable. + try + { + var dispatcher = scope.ServiceProvider.GetRequiredService(); + var factory = scope.ServiceProvider.GetRequiredService(); + var transaction = await factory.CreateTransactionAsync(); - var keys = await HandleCryptographyResponseAsync(); - if (keys is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0147)); - } + var request = new OpenIddictRequest(); + request = await PrepareCryptographyRequestAsync(); + request = await ApplyCryptographyRequestAsync(); - return keys; + var response = await ExtractCryptographyResponseAsync(); - async ValueTask PrepareCryptographyRequestAsync() - { - var context = new PrepareCryptographyRequestContext(transaction) - { - Address = address, - Request = request - }; - - await dispatcher.DispatchAsync(context); - - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0152(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } - - return context.Request; - } + var keys = await HandleCryptographyResponseAsync(); + if (keys is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0147)); + } - async ValueTask ApplyCryptographyRequestAsync() - { - var context = new ApplyCryptographyRequestContext(transaction) - { - Request = request - }; + return keys; - await dispatcher.DispatchAsync(context); + async ValueTask PrepareCryptographyRequestAsync() + { + var context = new PrepareCryptographyRequestContext(transaction) + { + Address = address, + Request = request + }; - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0153(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } + await dispatcher.DispatchAsync(context); - return context.Request; + if (context.IsRejected) + { + throw new OpenIddictExceptions.GenericException( + SR.FormatID0152(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); } - async ValueTask ExtractCryptographyResponseAsync() + return context.Request; + } + + async ValueTask ApplyCryptographyRequestAsync() + { + var context = new ApplyCryptographyRequestContext(transaction) { - var context = new ExtractCryptographyResponseContext(transaction) - { - Request = request - }; + Request = request + }; - await dispatcher.DispatchAsync(context); + await dispatcher.DispatchAsync(context); - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0154(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } + if (context.IsRejected) + { + throw new OpenIddictExceptions.GenericException( + SR.FormatID0153(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); + } - Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007)); + return context.Request; + } - return context.Response; - } + async ValueTask ExtractCryptographyResponseAsync() + { + var context = new ExtractCryptographyResponseContext(transaction) + { + Request = request + }; + + await dispatcher.DispatchAsync(context); - async ValueTask HandleCryptographyResponseAsync() + if (context.IsRejected) { - var context = new HandleCryptographyResponseContext(transaction) - { - Request = request, - Response = response - }; - - await dispatcher.DispatchAsync(context); - - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0155(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } - - return context.SecurityKeys; + throw new OpenIddictExceptions.GenericException( + SR.FormatID0154(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); } + + Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007)); + + return context.Response; } - finally + async ValueTask HandleCryptographyResponseAsync() { - if (scope is IAsyncDisposable disposable) + var context = new HandleCryptographyResponseContext(transaction) { - await disposable.DisposeAsync(); - } + Request = request, + Response = response + }; - else + await dispatcher.DispatchAsync(context); + + if (context.IsRejected) { - scope.Dispose(); + throw new OpenIddictExceptions.GenericException( + SR.FormatID0155(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); } + + return context.SecurityKeys; } } - /// - /// Sends an introspection request to the specified address and returns the corresponding principal. - /// - /// The address of the remote metadata endpoint. - /// The token to introspect. - /// The that can be used to abort the operation. - /// The claims principal created from the claim retrieved from the remote server. - public ValueTask IntrospectTokenAsync(Uri address, string token, CancellationToken cancellationToken = default) - => IntrospectTokenAsync(address, token, hint: null, cancellationToken); - - /// - /// Sends an introspection request to the specified address and returns the corresponding principal. - /// - /// The address of the remote metadata endpoint. - /// The token to introspect. - /// The token type to introspect, used as a hint by the authorization server. - /// The that can be used to abort the operation. - /// The claims principal created from the claim retrieved from the remote server. - public async ValueTask IntrospectTokenAsync( - Uri address, string token, string? hint, CancellationToken cancellationToken = default) + finally { - if (address is null) + if (scope is IAsyncDisposable disposable) { - throw new ArgumentNullException(nameof(address)); + await disposable.DisposeAsync(); } - if (!address.IsAbsoluteUri) + else { - throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address)); - } - - if (string.IsNullOrEmpty(token)) - { - throw new ArgumentException(SR.GetResourceString(SR.ID0156), nameof(token)); + scope.Dispose(); } + } + } - cancellationToken.ThrowIfCancellationRequested(); + /// + /// Sends an introspection request to the specified address and returns the corresponding principal. + /// + /// The address of the remote metadata endpoint. + /// The token to introspect. + /// The that can be used to abort the operation. + /// The claims principal created from the claim retrieved from the remote server. + public ValueTask IntrospectTokenAsync(Uri address, string token, CancellationToken cancellationToken = default) + => IntrospectTokenAsync(address, token, hint: null, cancellationToken); + + /// + /// Sends an introspection request to the specified address and returns the corresponding principal. + /// + /// The address of the remote metadata endpoint. + /// The token to introspect. + /// The token type to introspect, used as a hint by the authorization server. + /// The that can be used to abort the operation. + /// The claims principal created from the claim retrieved from the remote server. + public async ValueTask IntrospectTokenAsync( + Uri address, string token, string? hint, CancellationToken cancellationToken = default) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } - // Note: this service is registered as a singleton service. As such, it cannot - // directly depend on scoped services like the validation provider. To work around - // this limitation, a scope is manually created for each method to this service. - var scope = _provider.CreateScope(); + if (!address.IsAbsoluteUri) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address)); + } - // Note: a try/finally block is deliberately used here to ensure the service scope - // can be disposed of asynchronously if it implements IAsyncDisposable. - try - { - var dispatcher = scope.ServiceProvider.GetRequiredService(); - var factory = scope.ServiceProvider.GetRequiredService(); - var transaction = await factory.CreateTransactionAsync(); + if (string.IsNullOrEmpty(token)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0156), nameof(token)); + } - var request = new OpenIddictRequest(); - request = await PrepareIntrospectionRequestAsync(); - request = await ApplyIntrospectionRequestAsync(); - var response = await ExtractIntrospectionResponseAsync(); + cancellationToken.ThrowIfCancellationRequested(); - var principal = await HandleIntrospectionResponseAsync(); - if (principal is null) - { - throw new InvalidOperationException(SR.GetResourceString(SR.ID0157)); - } + // Note: this service is registered as a singleton service. As such, it cannot + // directly depend on scoped services like the validation provider. To work around + // this limitation, a scope is manually created for each method to this service. + var scope = _provider.CreateScope(); - return principal; + // Note: a try/finally block is deliberately used here to ensure the service scope + // can be disposed of asynchronously if it implements IAsyncDisposable. + try + { + var dispatcher = scope.ServiceProvider.GetRequiredService(); + var factory = scope.ServiceProvider.GetRequiredService(); + var transaction = await factory.CreateTransactionAsync(); - async ValueTask PrepareIntrospectionRequestAsync() - { - var context = new PrepareIntrospectionRequestContext(transaction) - { - Address = address, - Request = request, - Token = token, - TokenTypeHint = hint - }; - - await dispatcher.DispatchAsync(context); - - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0158(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } - - return context.Request; - } + var request = new OpenIddictRequest(); + request = await PrepareIntrospectionRequestAsync(); + request = await ApplyIntrospectionRequestAsync(); + var response = await ExtractIntrospectionResponseAsync(); - async ValueTask ApplyIntrospectionRequestAsync() - { - var context = new ApplyIntrospectionRequestContext(transaction) - { - Request = request - }; + var principal = await HandleIntrospectionResponseAsync(); + if (principal is null) + { + throw new InvalidOperationException(SR.GetResourceString(SR.ID0157)); + } - await dispatcher.DispatchAsync(context); + return principal; - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0159(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } + async ValueTask PrepareIntrospectionRequestAsync() + { + var context = new PrepareIntrospectionRequestContext(transaction) + { + Address = address, + Request = request, + Token = token, + TokenTypeHint = hint + }; - return context.Request; - } + await dispatcher.DispatchAsync(context); - async ValueTask ExtractIntrospectionResponseAsync() + if (context.IsRejected) { - var context = new ExtractIntrospectionResponseContext(transaction) - { - Request = request - }; + throw new OpenIddictExceptions.GenericException( + SR.FormatID0158(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); + } - await dispatcher.DispatchAsync(context); + return context.Request; + } - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0160(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } + async ValueTask ApplyIntrospectionRequestAsync() + { + var context = new ApplyIntrospectionRequestContext(transaction) + { + Request = request + }; - Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007)); + await dispatcher.DispatchAsync(context); - return context.Response; + if (context.IsRejected) + { + throw new OpenIddictExceptions.GenericException( + SR.FormatID0159(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); } - async ValueTask HandleIntrospectionResponseAsync() + return context.Request; + } + + async ValueTask ExtractIntrospectionResponseAsync() + { + var context = new ExtractIntrospectionResponseContext(transaction) { - var context = new HandleIntrospectionResponseContext(transaction) - { - Request = request, - Response = response, - Token = token - }; + Request = request + }; - await dispatcher.DispatchAsync(context); + await dispatcher.DispatchAsync(context); - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0161(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } + if (context.IsRejected) + { + throw new OpenIddictExceptions.GenericException( + SR.FormatID0160(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); + } - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007)); - return context.Principal; - } + return context.Response; } - finally + async ValueTask HandleIntrospectionResponseAsync() { - if (scope is IAsyncDisposable disposable) + var context = new HandleIntrospectionResponseContext(transaction) { - await disposable.DisposeAsync(); - } + Request = request, + Response = response, + Token = token + }; + + await dispatcher.DispatchAsync(context); - else + if (context.IsRejected) { - scope.Dispose(); + throw new OpenIddictExceptions.GenericException( + SR.FormatID0161(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); } + + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + + return context.Principal; } } - /// - /// Validates the specified access token and returns the principal extracted from the token. - /// - /// The access token to validate. - /// The that can be used to abort the operation. - /// The principal containing the claims extracted from the token. - public async ValueTask ValidateAccessTokenAsync(string token, CancellationToken cancellationToken = default) + finally { - if (string.IsNullOrEmpty(token)) + if (scope is IAsyncDisposable disposable) { - throw new ArgumentException(SR.GetResourceString(SR.ID0162), nameof(token)); + await disposable.DisposeAsync(); } - cancellationToken.ThrowIfCancellationRequested(); + else + { + scope.Dispose(); + } + } + } - // Note: this service is registered as a singleton service. As such, it cannot - // directly depend on scoped services like the validation provider. To work around - // this limitation, a scope is manually created for each method to this service. - var scope = _provider.CreateScope(); + /// + /// Validates the specified access token and returns the principal extracted from the token. + /// + /// The access token to validate. + /// The that can be used to abort the operation. + /// The principal containing the claims extracted from the token. + public async ValueTask ValidateAccessTokenAsync(string token, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(token)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0162), nameof(token)); + } - // Note: a try/finally block is deliberately used here to ensure the service scope - // can be disposed of asynchronously if it implements IAsyncDisposable. - try - { - var dispatcher = scope.ServiceProvider.GetRequiredService(); - var factory = scope.ServiceProvider.GetRequiredService(); - var transaction = await factory.CreateTransactionAsync(); + cancellationToken.ThrowIfCancellationRequested(); - var context = new ValidateTokenContext(transaction) - { - Token = token, - ValidTokenTypes = { TokenTypeHints.AccessToken } - }; + // Note: this service is registered as a singleton service. As such, it cannot + // directly depend on scoped services like the validation provider. To work around + // this limitation, a scope is manually created for each method to this service. + var scope = _provider.CreateScope(); - await dispatcher.DispatchAsync(context); + // Note: a try/finally block is deliberately used here to ensure the service scope + // can be disposed of asynchronously if it implements IAsyncDisposable. + try + { + var dispatcher = scope.ServiceProvider.GetRequiredService(); + var factory = scope.ServiceProvider.GetRequiredService(); + var transaction = await factory.CreateTransactionAsync(); - if (context.IsRejected) - { - throw new OpenIddictExceptions.GenericException( - SR.FormatID0163(context.Error, context.ErrorDescription, context.ErrorUri), - context.Error, context.ErrorDescription, context.ErrorUri); - } + var context = new ValidateTokenContext(transaction) + { + Token = token, + ValidTokenTypes = { TokenTypeHints.AccessToken } + }; - Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + await dispatcher.DispatchAsync(context); - return context.Principal; + if (context.IsRejected) + { + throw new OpenIddictExceptions.GenericException( + SR.FormatID0163(context.Error, context.ErrorDescription, context.ErrorUri), + context.Error, context.ErrorDescription, context.ErrorUri); } - finally + Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + + return context.Principal; + } + + finally + { + if (scope is IAsyncDisposable disposable) { - if (scope is IAsyncDisposable disposable) - { - await disposable.DisposeAsync(); - } + await disposable.DisposeAsync(); + } - else - { - scope.Dispose(); - } + else + { + scope.Dispose(); } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationTransaction.cs b/src/OpenIddict.Validation/OpenIddictValidationTransaction.cs index 7377c3f7..dc77bab8 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationTransaction.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationTransaction.cs @@ -9,46 +9,45 @@ using System.Collections.Generic; using Microsoft.Extensions.Logging; using OpenIddict.Abstractions; -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Represents the context associated with an OpenID Connect validation request. +/// +public class OpenIddictValidationTransaction { /// - /// Represents the context associated with an OpenID Connect validation request. + /// Gets or sets the type of the endpoint processing the current request. + /// + public OpenIddictValidationEndpointType EndpointType { get; set; } + + /// + /// Gets or sets the issuer address associated with the current transaction, if available. + /// + public Uri? Issuer { get; set; } + + /// + /// Gets or sets the logger associated with the current request. + /// + public ILogger Logger { get; set; } = default!; + + /// + /// Gets or sets the options associated with the current request. + /// + public OpenIddictValidationOptions Options { get; set; } = default!; + + /// + /// Gets the additional properties associated with the current request. + /// + public Dictionary Properties { get; } = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets or sets the current OpenID Connect request. + /// + public OpenIddictRequest? Request { get; set; } + + /// + /// Gets or sets the current OpenID Connect response being returned. /// - public class OpenIddictValidationTransaction - { - /// - /// Gets or sets the type of the endpoint processing the current request. - /// - public OpenIddictValidationEndpointType EndpointType { get; set; } - - /// - /// Gets or sets the issuer address associated with the current transaction, if available. - /// - public Uri? Issuer { get; set; } - - /// - /// Gets or sets the logger associated with the current request. - /// - public ILogger Logger { get; set; } = default!; - - /// - /// Gets or sets the options associated with the current request. - /// - public OpenIddictValidationOptions Options { get; set; } = default!; - - /// - /// Gets the additional properties associated with the current request. - /// - public Dictionary Properties { get; } = new(StringComparer.OrdinalIgnoreCase); - - /// - /// Gets or sets the current OpenID Connect request. - /// - public OpenIddictRequest? Request { get; set; } - - /// - /// Gets or sets the current OpenID Connect response being returned. - /// - public OpenIddictResponse? Response { get; set; } - } + public OpenIddictResponse? Response { get; set; } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationType.cs b/src/OpenIddict.Validation/OpenIddictValidationType.cs index 16c26422..5c924afb 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationType.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationType.cs @@ -4,26 +4,25 @@ * the license and the contributors participating to this project. */ -namespace OpenIddict.Validation +namespace OpenIddict.Validation; + +/// +/// Represents the type of validation performed by the OpenIddict validation services. +/// +public enum OpenIddictValidationType { /// - /// Represents the type of validation performed by the OpenIddict validation services. + /// Configures the OpenIddict validation services to use direct validation. + /// By default, direct validation uses IdentityModel to validate JWT tokens, + /// but a different token format can be used by registering the corresponding + /// package (e.g OpenIddict.Validation.DataProtection, for Data Protection tokens). /// - public enum OpenIddictValidationType - { - /// - /// Configures the OpenIddict validation services to use direct validation. - /// By default, direct validation uses IdentityModel to validate JWT tokens, - /// but a different token format can be used by registering the corresponding - /// package (e.g OpenIddict.Validation.DataProtection, for Data Protection tokens). - /// - Direct = 0, + Direct = 0, - /// - /// Configures the OpenIddict validation services to use introspection. - /// When using introspection, an OAuth 2.0 introspection request is sent - /// to the authorization server to validate the received access token. - /// - Introspection = 1 - } + /// + /// Configures the OpenIddict validation services to use introspection. + /// When using introspection, an OAuth 2.0 introspection request is sent + /// to the authorization server to validate the received access token. + /// + Introspection = 1 } diff --git a/test/OpenIddict.Abstractions.Tests/OpenIddictBuilderTests.cs b/test/OpenIddict.Abstractions.Tests/OpenIddictBuilderTests.cs index 6b85cfea..d4707efe 100644 --- a/test/OpenIddict.Abstractions.Tests/OpenIddictBuilderTests.cs +++ b/test/OpenIddict.Abstractions.Tests/OpenIddictBuilderTests.cs @@ -8,20 +8,19 @@ using System; using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace OpenIddict.Abstractions.Tests +namespace OpenIddict.Abstractions.Tests; + +public class OpenIddictBuilderTests { - public class OpenIddictBuilderTests + [Fact] + public void Constructor_ThrowsAnExceptionForNullServices() { - [Fact] - public void Constructor_ThrowsAnExceptionForNullServices() - { - // Arrange - var services = (IServiceCollection) null!; + // Arrange + var services = (IServiceCollection) null!; - // Act and assert - var exception = Assert.Throws(() => new OpenIddictBuilder(services)); + // Act and assert + var exception = Assert.Throws(() => new OpenIddictBuilder(services)); - Assert.Equal("services", exception.ParamName); - } + Assert.Equal("services", exception.ParamName); } } diff --git a/test/OpenIddict.Abstractions.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Abstractions.Tests/OpenIddictExtensionsTests.cs index 9a91ba3d..d55e1ec9 100644 --- a/test/OpenIddict.Abstractions.Tests/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.Abstractions.Tests/OpenIddictExtensionsTests.cs @@ -8,32 +8,31 @@ using System; using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace OpenIddict.Abstractions.Tests +namespace OpenIddict.Abstractions.Tests; + +public class OpenIddictExtensionsTests { - public class OpenIddictExtensionsTests + [Fact] + public void AddOpenIddict_ThrowsAnExceptionForNullServices() { - [Fact] - public void AddOpenIddict_ThrowsAnExceptionForNullServices() - { - // Arrange - var services = (IServiceCollection) null!; + // Arrange + var services = (IServiceCollection) null!; - // Act and assert - var exception = Assert.Throws(() => services.AddOpenIddict()); + // Act and assert + var exception = Assert.Throws(() => services.AddOpenIddict()); - Assert.Equal("services", exception.ParamName); - } + Assert.Equal("services", exception.ParamName); + } - [Fact] - public void AddOpenIddict_ThrowsAnExceptionForNullConfigurationDelegate() - { - // Arrange - var services = new ServiceCollection(); + [Fact] + public void AddOpenIddict_ThrowsAnExceptionForNullConfigurationDelegate() + { + // Arrange + var services = new ServiceCollection(); - // Act and assert - var exception = Assert.Throws(() => services.AddOpenIddict(configuration: null!)); + // Act and assert + var exception = Assert.Throws(() => services.AddOpenIddict(configuration: null!)); - Assert.Equal("configuration", exception.ParamName); - } + Assert.Equal("configuration", exception.ParamName); } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs index 93206659..1aa202fd 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs +++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictConverterTests.cs @@ -11,265 +11,264 @@ using System.Text.Json; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Abstractions.Tests.Primitives +namespace OpenIddict.Abstractions.Tests.Primitives; + +public class OpenIddictConverterTests { - public class OpenIddictConverterTests + [Fact] + public void CanConvert_ThrowsAnExceptionForNullType() { - [Fact] - public void CanConvert_ThrowsAnExceptionForNullType() - { - // Arrange - var converter = new OpenIddictConverter(); - - // Act and assert - var exception = Assert.Throws(() => converter.CanConvert(typeToConvert: null!)); - - Assert.Equal("typeToConvert", exception.ParamName); - } - - [Theory] - [InlineData(typeof(OpenIddictMessage), true)] - [InlineData(typeof(OpenIddictRequest), true)] - [InlineData(typeof(OpenIddictResponse), true)] - [InlineData(typeof(OpenIddictMessage[]), false)] - [InlineData(typeof(OpenIddictRequest[]), false)] - [InlineData(typeof(OpenIddictResponse[]), false)] - [InlineData(typeof(OpenIddictParameter), false)] - [InlineData(typeof(OpenIddictParameter?), false)] - [InlineData(typeof(OpenIddictParameter[]), false)] - [InlineData(typeof(OpenIddictParameter?[]), false)] - [InlineData(typeof(object), false)] - [InlineData(typeof(long), false)] - public void CanConvert_ReturnsExpectedResult(Type type, bool result) - { - // Arrange - var converter = new OpenIddictConverter(); + // Arrange + var converter = new OpenIddictConverter(); - // Act and assert - Assert.Equal(result, converter.CanConvert(type)); - } + // Act and assert + var exception = Assert.Throws(() => converter.CanConvert(typeToConvert: null!)); - [Fact] - public void Read_ThrowsAnExceptionForNullType() - { - // Arrange - var converter = new OpenIddictConverter(); - - // Act and assert - var exception = Assert.Throws(delegate - { - var reader = new Utf8JsonReader(); - return converter.Read(ref reader, typeToConvert: null!, options: null!); - }); - - Assert.Equal("typeToConvert", exception.ParamName); - } - - [Theory] - [InlineData(typeof(OpenIddictMessage[]))] - [InlineData(typeof(OpenIddictRequest[]))] - [InlineData(typeof(OpenIddictResponse[]))] - [InlineData(typeof(OpenIddictParameter))] - [InlineData(typeof(OpenIddictParameter?))] - [InlineData(typeof(OpenIddictParameter[]))] - [InlineData(typeof(OpenIddictParameter?[]))] - [InlineData(typeof(object))] - [InlineData(typeof(long))] - public void Read_ThrowsAnExceptionForUnsupportedType(Type type) + Assert.Equal("typeToConvert", exception.ParamName); + } + + [Theory] + [InlineData(typeof(OpenIddictMessage), true)] + [InlineData(typeof(OpenIddictRequest), true)] + [InlineData(typeof(OpenIddictResponse), true)] + [InlineData(typeof(OpenIddictMessage[]), false)] + [InlineData(typeof(OpenIddictRequest[]), false)] + [InlineData(typeof(OpenIddictResponse[]), false)] + [InlineData(typeof(OpenIddictParameter), false)] + [InlineData(typeof(OpenIddictParameter?), false)] + [InlineData(typeof(OpenIddictParameter[]), false)] + [InlineData(typeof(OpenIddictParameter?[]), false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(long), false)] + public void CanConvert_ReturnsExpectedResult(Type type, bool result) + { + // Arrange + var converter = new OpenIddictConverter(); + + // Act and assert + Assert.Equal(result, converter.CanConvert(type)); + } + + [Fact] + public void Read_ThrowsAnExceptionForNullType() + { + // Arrange + var converter = new OpenIddictConverter(); + + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var converter = new OpenIddictConverter(); - - // Act and assert - var exception = Assert.Throws(delegate - { - var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"{""name"":""value""}")); - return converter.Read(ref reader, type, options: null!); - }); - - Assert.StartsWith(SR.GetResourceString(SR.ID0176), exception.Message); - Assert.Equal("typeToConvert", exception.ParamName); - } - - [Theory] - [InlineData(typeof(OpenIddictMessage))] - [InlineData(typeof(OpenIddictRequest))] - [InlineData(typeof(OpenIddictResponse))] - public void Read_ReturnsRequestedType(Type type) + var reader = new Utf8JsonReader(); + return converter.Read(ref reader, typeToConvert: null!, options: null!); + }); + + Assert.Equal("typeToConvert", exception.ParamName); + } + + [Theory] + [InlineData(typeof(OpenIddictMessage[]))] + [InlineData(typeof(OpenIddictRequest[]))] + [InlineData(typeof(OpenIddictResponse[]))] + [InlineData(typeof(OpenIddictParameter))] + [InlineData(typeof(OpenIddictParameter?))] + [InlineData(typeof(OpenIddictParameter[]))] + [InlineData(typeof(OpenIddictParameter?[]))] + [InlineData(typeof(object))] + [InlineData(typeof(long))] + public void Read_ThrowsAnExceptionForUnsupportedType(Type type) + { + // Arrange + var converter = new OpenIddictConverter(); + + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var converter = new OpenIddictConverter(); var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"{""name"":""value""}")); + return converter.Read(ref reader, type, options: null!); + }); + + Assert.StartsWith(SR.GetResourceString(SR.ID0176), exception.Message); + Assert.Equal("typeToConvert", exception.ParamName); + } - // Act - var message = converter.Read(ref reader, type, options: null!); + [Theory] + [InlineData(typeof(OpenIddictMessage))] + [InlineData(typeof(OpenIddictRequest))] + [InlineData(typeof(OpenIddictResponse))] + public void Read_ReturnsRequestedType(Type type) + { + // Arrange + var converter = new OpenIddictConverter(); + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"{""name"":""value""}")); - // Assert - Assert.IsType(type, message); - Assert.Equal("value", (string?) message.GetParameter("name")); - } + // Act + var message = converter.Read(ref reader, type, options: null!); - [Fact] - public void Read_PreservesNullParameters() - { - // Arrange - var converter = new OpenIddictConverter(); - var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes( - @"{""string"":null,""bool"":null,""long"":null,""array"":null,""object"":null}")); - - // Act - var message = converter.Read(ref reader, typeof(OpenIddictMessage), options: null!); - - // Assert - Assert.Equal(5, message.Count); - Assert.NotNull(message.GetParameter("string")); - Assert.NotNull(message.GetParameter("bool")); - Assert.NotNull(message.GetParameter("long")); - Assert.NotNull(message.GetParameter("array")); - Assert.NotNull(message.GetParameter("object")); - Assert.Null((string?) message.GetParameter("string")); - Assert.Null((bool?) message.GetParameter("bool")); - Assert.Null((long?) message.GetParameter("long")); - Assert.Equal(JsonValueKind.Null, ((JsonElement) message.GetParameter("array")).ValueKind); - Assert.Equal(JsonValueKind.Null, ((JsonElement) message.GetParameter("object")).ValueKind); - } - - [Fact] - public void Read_PreservesEmptyParameters() - { - // Arrange - var converter = new OpenIddictConverter(); - var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"{""string"":"""",""array"":[],""object"":{}}")); - - // Act - var message = converter.Read(ref reader, typeof(OpenIddictMessage), options: null!); - - // Assert - Assert.Equal(3, message.Count); - Assert.NotNull(message.GetParameter("string")); - Assert.NotNull(message.GetParameter("array")); - Assert.NotNull(message.GetParameter("object")); - Assert.Empty((string?) message.GetParameter("string")); - Assert.NotNull((JsonElement?) message.GetParameter("array")); - Assert.NotNull((JsonElement?) message.GetParameter("object")); - } - - [Fact] - public void Write_ThrowsAnExceptionForNullWriter() - { - // Arrange - var converter = new OpenIddictConverter(); + // Assert + Assert.IsType(type, message); + Assert.Equal("value", (string?) message.GetParameter("name")); + } - // Act and assert - var exception = Assert.Throws(() => - { - converter.Write(writer: null!, value: null!, options: null!); - }); + [Fact] + public void Read_PreservesNullParameters() + { + // Arrange + var converter = new OpenIddictConverter(); + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes( + @"{""string"":null,""bool"":null,""long"":null,""array"":null,""object"":null}")); + + // Act + var message = converter.Read(ref reader, typeof(OpenIddictMessage), options: null!); + + // Assert + Assert.Equal(5, message.Count); + Assert.NotNull(message.GetParameter("string")); + Assert.NotNull(message.GetParameter("bool")); + Assert.NotNull(message.GetParameter("long")); + Assert.NotNull(message.GetParameter("array")); + Assert.NotNull(message.GetParameter("object")); + Assert.Null((string?) message.GetParameter("string")); + Assert.Null((bool?) message.GetParameter("bool")); + Assert.Null((long?) message.GetParameter("long")); + Assert.Equal(JsonValueKind.Null, ((JsonElement) message.GetParameter("array")).ValueKind); + Assert.Equal(JsonValueKind.Null, ((JsonElement) message.GetParameter("object")).ValueKind); + } + + [Fact] + public void Read_PreservesEmptyParameters() + { + // Arrange + var converter = new OpenIddictConverter(); + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"{""string"":"""",""array"":[],""object"":{}}")); + + // Act + var message = converter.Read(ref reader, typeof(OpenIddictMessage), options: null!); + + // Assert + Assert.Equal(3, message.Count); + Assert.NotNull(message.GetParameter("string")); + Assert.NotNull(message.GetParameter("array")); + Assert.NotNull(message.GetParameter("object")); + Assert.Empty((string?) message.GetParameter("string")); + Assert.NotNull((JsonElement?) message.GetParameter("array")); + Assert.NotNull((JsonElement?) message.GetParameter("object")); + } - Assert.Equal("writer", exception.ParamName); - } + [Fact] + public void Write_ThrowsAnExceptionForNullWriter() + { + // Arrange + var converter = new OpenIddictConverter(); - [Fact] - public void Write_ThrowsAnExceptionForNullValue() + // Act and assert + var exception = Assert.Throws(() => { - // Arrange - var converter = new OpenIddictConverter(); + converter.Write(writer: null!, value: null!, options: null!); + }); - // Act and assert - var exception = Assert.Throws(() => - { - converter.Write(writer: new Utf8JsonWriter(Stream.Null), value: null!, options: null!); - }); + Assert.Equal("writer", exception.ParamName); + } - Assert.Equal("value", exception.ParamName); - } + [Fact] + public void Write_ThrowsAnExceptionForNullValue() + { + // Arrange + var converter = new OpenIddictConverter(); - [Fact] - public void Write_WritesEmptyPayloadForEmptyMessages() - { - // Arrange - var message = new OpenIddictMessage(); - var converter = new OpenIddictConverter(); - using var stream = new MemoryStream(); - using var reader = new StreamReader(stream); - using var writer = new Utf8JsonWriter(stream); - - // Act - converter.Write(writer, value: message, options: null!); - - // Assert - writer.Flush(); - stream.Seek(0L, SeekOrigin.Begin); - Assert.Equal("{}", reader.ReadToEnd()); - } - - [Fact] - public void Write_PreservesNullParameters() + // Act and assert + var exception = Assert.Throws(() => { - // Arrange - var converter = new OpenIddictConverter(); - using var stream = new MemoryStream(); - using var reader = new StreamReader(stream); - using var writer = new Utf8JsonWriter(stream); - - var message = new OpenIddictMessage(); - message.AddParameter("string", new OpenIddictParameter((string?) null)); - message.AddParameter("bool", new OpenIddictParameter((bool?) null)); - message.AddParameter("long", new OpenIddictParameter((long?) null)); - message.AddParameter("node", new OpenIddictParameter(default(JsonElement))); - - // Act - converter.Write(writer, value: message, options: null!); - - // Assert - writer.Flush(); - stream.Seek(0L, SeekOrigin.Begin); - Assert.Equal(@"{""string"":null,""bool"":null,""long"":null,""node"":null}", reader.ReadToEnd()); - } - - [Fact] - public void Write_PreservesEmptyParameters() - { - // Arrange - var converter = new OpenIddictConverter(); - using var stream = new MemoryStream(); - using var reader = new StreamReader(stream); - using var writer = new Utf8JsonWriter(stream); - - var message = new OpenIddictMessage(); - message.AddParameter("string", new OpenIddictParameter(string.Empty)); - message.AddParameter("array", new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); - message.AddParameter("object", new OpenIddictParameter(JsonSerializer.Deserialize("{}"))); - - // Act - converter.Write(writer, value: message, options: null!); - - // Assert - writer.Flush(); - stream.Seek(0L, SeekOrigin.Begin); - Assert.Equal(@"{""string"":"""",""array"":[],""object"":{}}", reader.ReadToEnd()); - } - - [Fact] - public void Write_WritesExpectedPayload() - { - // Arrange - var converter = new OpenIddictConverter(); - using var stream = new MemoryStream(); - using var reader = new StreamReader(stream); - using var writer = new Utf8JsonWriter(stream); - - var message = new OpenIddictMessage(); - message.AddParameter("string", "value"); - message.AddParameter("array", new[] { "value" }); - - // Act - converter.Write(writer, value: message, options: null!); - - // Assert - writer.Flush(); - stream.Seek(0L, SeekOrigin.Begin); - Assert.Equal(@"{""string"":""value"",""array"":[""value""]}", reader.ReadToEnd()); - } + converter.Write(writer: new Utf8JsonWriter(Stream.Null), value: null!, options: null!); + }); + + Assert.Equal("value", exception.ParamName); + } + + [Fact] + public void Write_WritesEmptyPayloadForEmptyMessages() + { + // Arrange + var message = new OpenIddictMessage(); + var converter = new OpenIddictConverter(); + using var stream = new MemoryStream(); + using var reader = new StreamReader(stream); + using var writer = new Utf8JsonWriter(stream); + + // Act + converter.Write(writer, value: message, options: null!); + + // Assert + writer.Flush(); + stream.Seek(0L, SeekOrigin.Begin); + Assert.Equal("{}", reader.ReadToEnd()); + } + + [Fact] + public void Write_PreservesNullParameters() + { + // Arrange + var converter = new OpenIddictConverter(); + using var stream = new MemoryStream(); + using var reader = new StreamReader(stream); + using var writer = new Utf8JsonWriter(stream); + + var message = new OpenIddictMessage(); + message.AddParameter("string", new OpenIddictParameter((string?) null)); + message.AddParameter("bool", new OpenIddictParameter((bool?) null)); + message.AddParameter("long", new OpenIddictParameter((long?) null)); + message.AddParameter("node", new OpenIddictParameter(default(JsonElement))); + + // Act + converter.Write(writer, value: message, options: null!); + + // Assert + writer.Flush(); + stream.Seek(0L, SeekOrigin.Begin); + Assert.Equal(@"{""string"":null,""bool"":null,""long"":null,""node"":null}", reader.ReadToEnd()); + } + + [Fact] + public void Write_PreservesEmptyParameters() + { + // Arrange + var converter = new OpenIddictConverter(); + using var stream = new MemoryStream(); + using var reader = new StreamReader(stream); + using var writer = new Utf8JsonWriter(stream); + + var message = new OpenIddictMessage(); + message.AddParameter("string", new OpenIddictParameter(string.Empty)); + message.AddParameter("array", new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); + message.AddParameter("object", new OpenIddictParameter(JsonSerializer.Deserialize("{}"))); + + // Act + converter.Write(writer, value: message, options: null!); + + // Assert + writer.Flush(); + stream.Seek(0L, SeekOrigin.Begin); + Assert.Equal(@"{""string"":"""",""array"":[],""object"":{}}", reader.ReadToEnd()); + } + + [Fact] + public void Write_WritesExpectedPayload() + { + // Arrange + var converter = new OpenIddictConverter(); + using var stream = new MemoryStream(); + using var reader = new StreamReader(stream); + using var writer = new Utf8JsonWriter(stream); + + var message = new OpenIddictMessage(); + message.AddParameter("string", "value"); + message.AddParameter("array", new[] { "value" }); + + // Act + converter.Write(writer, value: message, options: null!); + + // Assert + writer.Flush(); + stream.Seek(0L, SeekOrigin.Begin); + Assert.Equal(@"{""string"":""value"",""array"":[""value""]}", reader.ReadToEnd()); } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs index 79a607c5..9a60d227 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs +++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs @@ -14,2904 +14,2903 @@ using Xunit; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Abstractions.Tests.Primitives +namespace OpenIddict.Abstractions.Tests.Primitives; + +public class OpenIddictExtensionsTests { - public class OpenIddictExtensionsTests + [Fact] + public void GetAcrValues_ThrowsAnExceptionForNullRequest() { - [Fact] - public void GetAcrValues_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.GetAcrValues()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData("mod-pr", new[] { "mod-pr" })] - [InlineData("mod-pr ", new[] { "mod-pr" })] - [InlineData(" mod-pr ", new[] { "mod-pr" })] - [InlineData("mod-pr mod-mf", new[] { "mod-pr", "mod-mf" })] - [InlineData("mod-pr mod-mf", new[] { "mod-pr", "mod-mf" })] - [InlineData("mod-pr mod-mf ", new[] { "mod-pr", "mod-mf" })] - [InlineData(" mod-pr mod-mf", new[] { "mod-pr", "mod-mf" })] - [InlineData("mod-pr mod-pr mod-mf", new[] { "mod-pr", "mod-mf" })] - [InlineData("mod-pr MOD-PR mod-mf", new[] { "mod-pr", "MOD-PR", "mod-mf" })] - public void GetAcrValues_ReturnsExpectedAcrValues(string value, string[] values) - { - // Arrange - var request = new OpenIddictRequest - { - AcrValues = value - }; + // Arrange + var request = (OpenIddictRequest) null!; - // Act and assert - Assert.Equal(values, request.GetAcrValues()); - } + // Act and assert + var exception = Assert.Throws(() => request.GetAcrValues()); - [Fact] - public void GetPrompts_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act - var exception = Assert.Throws(() => request.GetPrompts()); - - // Assert - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData("login", new[] { "login" })] - [InlineData("login ", new[] { "login" })] - [InlineData(" login ", new[] { "login" })] - [InlineData("login consent", new[] { "login", "consent" })] - [InlineData("login consent", new[] { "login", "consent" })] - [InlineData("login consent ", new[] { "login", "consent" })] - [InlineData(" login consent", new[] { "login", "consent" })] - [InlineData("login login consent", new[] { "login", "consent" })] - [InlineData("login LOGIN consent", new[] { "login", "LOGIN", "consent" })] - public void GetPrompts_ReturnsExpectedPrompts(string value, string[] values) + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData("mod-pr", new[] { "mod-pr" })] + [InlineData("mod-pr ", new[] { "mod-pr" })] + [InlineData(" mod-pr ", new[] { "mod-pr" })] + [InlineData("mod-pr mod-mf", new[] { "mod-pr", "mod-mf" })] + [InlineData("mod-pr mod-mf", new[] { "mod-pr", "mod-mf" })] + [InlineData("mod-pr mod-mf ", new[] { "mod-pr", "mod-mf" })] + [InlineData(" mod-pr mod-mf", new[] { "mod-pr", "mod-mf" })] + [InlineData("mod-pr mod-pr mod-mf", new[] { "mod-pr", "mod-mf" })] + [InlineData("mod-pr MOD-PR mod-mf", new[] { "mod-pr", "MOD-PR", "mod-mf" })] + public void GetAcrValues_ReturnsExpectedAcrValues(string value, string[] values) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = new OpenIddictRequest - { - Prompt = value - }; + AcrValues = value + }; - // Act and assert - Assert.Equal(values, request.GetPrompts()); - } + // Act and assert + Assert.Equal(values, request.GetAcrValues()); + } - [Fact] - public void GetResponseTypes_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act - var exception = Assert.Throws(() => request.GetResponseTypes()); - - // Assert - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData("code", new[] { "code" })] - [InlineData("code ", new[] { "code" })] - [InlineData(" code ", new[] { "code" })] - [InlineData("code id_token", new[] { "code", "id_token" })] - [InlineData("code id_token", new[] { "code", "id_token" })] - [InlineData("code id_token ", new[] { "code", "id_token" })] - [InlineData(" code id_token", new[] { "code", "id_token" })] - [InlineData("code code id_token", new[] { "code", "id_token" })] - [InlineData("code CODE id_token", new[] { "code", "CODE", "id_token" })] - public void GetResponseTypes_ReturnsExpectedResponseTypes(string value, string[] values) - { - // Arrange - var request = new OpenIddictRequest - { - ResponseType = value - }; + [Fact] + public void GetPrompts_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - // Act and assert - Assert.Equal(values, request.GetResponseTypes()); - } + // Act + var exception = Assert.Throws(() => request.GetPrompts()); - [Fact] - public void GetScopes_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.GetScopes()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData("openid", new[] { "openid" })] - [InlineData("openid ", new[] { "openid" })] - [InlineData(" openid ", new[] { "openid" })] - [InlineData("openid profile", new[] { "openid", "profile" })] - [InlineData("openid profile", new[] { "openid", "profile" })] - [InlineData("openid profile ", new[] { "openid", "profile" })] - [InlineData(" openid profile", new[] { "openid", "profile" })] - [InlineData("openid openid profile", new[] { "openid", "profile" })] - [InlineData("openid OPENID profile", new[] { "openid", "OPENID", "profile" })] - public void GetScopes_ReturnsExpectedScopes(string scope, string[] scopes) + // Assert + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData("login", new[] { "login" })] + [InlineData("login ", new[] { "login" })] + [InlineData(" login ", new[] { "login" })] + [InlineData("login consent", new[] { "login", "consent" })] + [InlineData("login consent", new[] { "login", "consent" })] + [InlineData("login consent ", new[] { "login", "consent" })] + [InlineData(" login consent", new[] { "login", "consent" })] + [InlineData("login login consent", new[] { "login", "consent" })] + [InlineData("login LOGIN consent", new[] { "login", "LOGIN", "consent" })] + public void GetPrompts_ReturnsExpectedPrompts(string value, string[] values) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = new OpenIddictRequest - { - Scope = scope - }; + Prompt = value + }; - // Act and assert - Assert.Equal(scopes, request.GetScopes()); - } + // Act and assert + Assert.Equal(values, request.GetPrompts()); + } - [Fact] - public void HasAcrValue_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; + [Fact] + public void GetResponseTypes_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - // Act and assert - var exception = Assert.Throws(() => request.HasAcrValue("mod-mf")); + // Act + var exception = Assert.Throws(() => request.GetResponseTypes()); - Assert.Equal("request", exception.ParamName); - } + // Assert + Assert.Equal("request", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasAcrValue_ThrowsAnExceptionForNullOrEmptyAcrValue(string value) - { - // Arrange - var request = new OpenIddictRequest(); - - // Act and assert - var exception = Assert.Throws(() => request.HasAcrValue(value)); - - Assert.Equal("value", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0177), exception.Message); - } - - [Theory] - [InlineData(null, false)] - [InlineData("mod-mf", true)] - [InlineData("mod-mf mod-pr", true)] - [InlineData(" mod-mf mod-pr", true)] - [InlineData("mod-pr mod-mf", true)] - [InlineData("mod-pr mod-mf ", true)] - [InlineData("mod-pr mod-mf mod-cstm", true)] - [InlineData("mod-pr mod-mf mod-cstm ", true)] - [InlineData("mod-pr mod-mf mod-cstm ", true)] - [InlineData("mod-pr", false)] - [InlineData("mod-pr mod-cstm", false)] - [InlineData("MOD-MF", false)] - [InlineData("MOD-MF MOD-PR", false)] - [InlineData(" MOD-MF MOD-PR", false)] - [InlineData("MOD-PR MOD-MF", false)] - [InlineData("MOD-PR MOD-MF ", false)] - [InlineData("MOD-PR MOD-MF MOD-CSTM", false)] - [InlineData("MOD-PR MOD-MF MOD-CSTM ", false)] - [InlineData("MOD-PR MOD-MF MOD-CSTM ", false)] - [InlineData("MOD-PR", false)] - [InlineData("MOD-PR MOD-CSTM", false)] - public void HasAcrValue_ReturnsExpectedResult(string value, bool result) + [Theory] + [InlineData(null, new string[0])] + [InlineData("code", new[] { "code" })] + [InlineData("code ", new[] { "code" })] + [InlineData(" code ", new[] { "code" })] + [InlineData("code id_token", new[] { "code", "id_token" })] + [InlineData("code id_token", new[] { "code", "id_token" })] + [InlineData("code id_token ", new[] { "code", "id_token" })] + [InlineData(" code id_token", new[] { "code", "id_token" })] + [InlineData("code code id_token", new[] { "code", "id_token" })] + [InlineData("code CODE id_token", new[] { "code", "CODE", "id_token" })] + public void GetResponseTypes_ReturnsExpectedResponseTypes(string value, string[] values) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = new OpenIddictRequest - { - AcrValues = value - }; + ResponseType = value + }; - // Act and assert - Assert.Equal(result, request.HasAcrValue("mod-mf")); - } + // Act and assert + Assert.Equal(values, request.GetResponseTypes()); + } - [Fact] - public void HasPrompt_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; + [Fact] + public void GetScopes_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - // Act and assert - var exception = Assert.Throws(() => - { - request.HasPrompt(Prompts.Consent); - }); + // Act and assert + var exception = Assert.Throws(() => request.GetScopes()); - Assert.Equal("request", exception.ParamName); - } + Assert.Equal("request", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasPrompt_ThrowsAnExceptionForNullOrEmptyPrompt(string prompt) - { - // Arrange - var request = new OpenIddictRequest(); - - // Act and assert - var exception = Assert.Throws(() => request.HasPrompt(prompt)); - - Assert.Equal("prompt", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0178), exception.Message); - } - - [Theory] - [InlineData(null, false)] - [InlineData("consent", true)] - [InlineData("consent login", true)] - [InlineData(" consent login", true)] - [InlineData("login consent", true)] - [InlineData("login consent ", true)] - [InlineData("login consent select_account", true)] - [InlineData("login consent select_account ", true)] - [InlineData("login consent select_account ", true)] - [InlineData("login", false)] - [InlineData("login select_account", false)] - [InlineData("CONSENT", false)] - [InlineData("CONSENT LOGIN", false)] - [InlineData(" CONSENT LOGIN", false)] - [InlineData("LOGIN CONSENT", false)] - [InlineData("LOGIN CONSENT ", false)] - [InlineData("LOGIN CONSENT SELECT_ACCOUNT", false)] - [InlineData("LOGIN CONSENT SELECT_ACCOUNT ", false)] - [InlineData("LOGIN CONSENT SELECT_ACCOUNT ", false)] - [InlineData("LOGIN", false)] - [InlineData("LOGIN SELECT_ACCOUNT", false)] - public void HasPrompt_ReturnsExpectedResult(string prompt, bool result) + [Theory] + [InlineData(null, new string[0])] + [InlineData("openid", new[] { "openid" })] + [InlineData("openid ", new[] { "openid" })] + [InlineData(" openid ", new[] { "openid" })] + [InlineData("openid profile", new[] { "openid", "profile" })] + [InlineData("openid profile", new[] { "openid", "profile" })] + [InlineData("openid profile ", new[] { "openid", "profile" })] + [InlineData(" openid profile", new[] { "openid", "profile" })] + [InlineData("openid openid profile", new[] { "openid", "profile" })] + [InlineData("openid OPENID profile", new[] { "openid", "OPENID", "profile" })] + public void GetScopes_ReturnsExpectedScopes(string scope, string[] scopes) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = new OpenIddictRequest - { - Prompt = prompt - }; + Scope = scope + }; - // Act and assert - Assert.Equal(result, request.HasPrompt(Prompts.Consent)); - } + // Act and assert + Assert.Equal(scopes, request.GetScopes()); + } - [Fact] - public void HasResponseType_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; + [Fact] + public void HasAcrValue_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - // Act and assert - var exception = Assert.Throws(() => - { - request.HasResponseType(ResponseTypes.Code); - }); + // Act and assert + var exception = Assert.Throws(() => request.HasAcrValue("mod-mf")); - Assert.Equal("request", exception.ParamName); - } + Assert.Equal("request", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasResponseType_ThrowsAnExceptionForNullOrEmptyResponseType(string type) - { - // Arrange - var request = new OpenIddictRequest(); - - // Act and assert - var exception = Assert.Throws(() => request.HasResponseType(type)); - - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0179), exception.Message); - } - - [Theory] - [InlineData(null, false)] - [InlineData("code", true)] - [InlineData("code id_token", true)] - [InlineData(" code id_token", true)] - [InlineData("id_token code", true)] - [InlineData("id_token code ", true)] - [InlineData("id_token code token", true)] - [InlineData("id_token code token ", true)] - [InlineData("id_token code token ", true)] - [InlineData("id_token", false)] - [InlineData("id_token token", false)] - [InlineData("CODE", false)] - [InlineData("CODE ID_TOKEN", false)] - [InlineData(" CODE ID_TOKEN", false)] - [InlineData("ID_TOKEN CODE", false)] - [InlineData("ID_TOKEN CODE ", false)] - [InlineData("ID_TOKEN CODE TOKEN", false)] - [InlineData("ID_TOKEN CODE TOKEN ", false)] - [InlineData("ID_TOKEN CODE TOKEN ", false)] - [InlineData("ID_TOKEN", false)] - [InlineData("ID_TOKEN TOKEN", false)] - public void HasResponseType_ReturnsExpectedResult(string type, bool result) - { - // Arrange - var request = new OpenIddictRequest - { - ResponseType = type - }; + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasAcrValue_ThrowsAnExceptionForNullOrEmptyAcrValue(string value) + { + // Arrange + var request = new OpenIddictRequest(); + + // Act and assert + var exception = Assert.Throws(() => request.HasAcrValue(value)); - // Act and assert - Assert.Equal(result, request.HasResponseType(ResponseTypes.Code)); - } + Assert.Equal("value", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0177), exception.Message); + } - [Fact] - public void HasScope_ThrowsAnExceptionForNullRequest() + [Theory] + [InlineData(null, false)] + [InlineData("mod-mf", true)] + [InlineData("mod-mf mod-pr", true)] + [InlineData(" mod-mf mod-pr", true)] + [InlineData("mod-pr mod-mf", true)] + [InlineData("mod-pr mod-mf ", true)] + [InlineData("mod-pr mod-mf mod-cstm", true)] + [InlineData("mod-pr mod-mf mod-cstm ", true)] + [InlineData("mod-pr mod-mf mod-cstm ", true)] + [InlineData("mod-pr", false)] + [InlineData("mod-pr mod-cstm", false)] + [InlineData("MOD-MF", false)] + [InlineData("MOD-MF MOD-PR", false)] + [InlineData(" MOD-MF MOD-PR", false)] + [InlineData("MOD-PR MOD-MF", false)] + [InlineData("MOD-PR MOD-MF ", false)] + [InlineData("MOD-PR MOD-MF MOD-CSTM", false)] + [InlineData("MOD-PR MOD-MF MOD-CSTM ", false)] + [InlineData("MOD-PR MOD-MF MOD-CSTM ", false)] + [InlineData("MOD-PR", false)] + [InlineData("MOD-PR MOD-CSTM", false)] + public void HasAcrValue_ReturnsExpectedResult(string value, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = (OpenIddictRequest) null!; + AcrValues = value + }; - // Act and assert - var exception = Assert.Throws(() => - { - request.HasScope(Scopes.OpenId); - }); + // Act and assert + Assert.Equal(result, request.HasAcrValue("mod-mf")); + } - Assert.Equal("request", exception.ParamName); - } + [Fact] + public void HasPrompt_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasScope_ThrowsAnExceptionForNullOrEmptyScope(string scope) - { - // Arrange - var request = new OpenIddictRequest(); - - // Act and assert - var exception = Assert.Throws(() => request.HasScope(scope)); - - Assert.Equal("scope", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0180), exception.Message); - } - - [Theory] - [InlineData(null, false)] - [InlineData("openid", true)] - [InlineData("openid ", true)] - [InlineData(" openid ", true)] - [InlineData("openid profile", true)] - [InlineData("openid profile", true)] - [InlineData("openid profile ", true)] - [InlineData(" openid profile", true)] - [InlineData("profile", false)] - [InlineData("profile email", false)] - [InlineData("OPENID", false)] - [InlineData("OPENID ", false)] - [InlineData(" OPENID ", false)] - [InlineData("OPENID PROFILE", false)] - [InlineData("OPENID PROFILE", false)] - [InlineData("OPENID PROFILE ", false)] - [InlineData(" OPENID PROFILE", false)] - [InlineData("PROFILE", false)] - [InlineData("PROFILE EMAIL", false)] - public void HasScope_ReturnsExpectedResult(string scope, bool result) + // Act and assert + var exception = Assert.Throws(() => { - // Arrange - var request = new OpenIddictRequest - { - Scope = scope - }; + request.HasPrompt(Prompts.Consent); + }); - // Act and assert - Assert.Equal(result, request.HasScope(Scopes.OpenId)); - } + Assert.Equal("request", exception.ParamName); + } - [Fact] - public void IsNoneFlow_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsNoneFlow()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("none", true)] - [InlineData("none ", true)] - [InlineData(" none", true)] - [InlineData("none id_token", false)] - [InlineData(" none id_token", false)] - [InlineData("none id_token ", false)] - [InlineData(" none id_token ", false)] - [InlineData("NONE", false)] - [InlineData("NONE ", false)] - [InlineData(" NONE", false)] - [InlineData("NONE ID_TOKEN", false)] - [InlineData(" NONE ID_TOKEN", false)] - [InlineData("NONE ID_TOKEN ", false)] - [InlineData(" NONE ID_TOKEN ", false)] - public void IsNoneFlow_ReturnsExpectedResult(string type, bool result) - { - // Arrange - var request = new OpenIddictRequest - { - ResponseType = type - }; + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasPrompt_ThrowsAnExceptionForNullOrEmptyPrompt(string prompt) + { + // Arrange + var request = new OpenIddictRequest(); - // Act and assert - Assert.Equal(result, request.IsNoneFlow()); - } + // Act and assert + var exception = Assert.Throws(() => request.HasPrompt(prompt)); - [Fact] - public void IsAuthorizationCodeFlow_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsAuthorizationCodeFlow()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("code", true)] - [InlineData("code ", true)] - [InlineData(" code", true)] - [InlineData("code id_token", false)] - [InlineData(" code id_token", false)] - [InlineData("code id_token ", false)] - [InlineData(" code id_token ", false)] - [InlineData("CODE", false)] - [InlineData("CODE ", false)] - [InlineData(" CODE", false)] - [InlineData("CODE ID_TOKEN", false)] - [InlineData(" CODE ID_TOKEN", false)] - [InlineData("CODE ID_TOKEN ", false)] - [InlineData(" CODE ID_TOKEN ", false)] - public void IsAuthorizationCodeFlow_ReturnsExpectedResult(string type, bool result) + Assert.Equal("prompt", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0178), exception.Message); + } + + [Theory] + [InlineData(null, false)] + [InlineData("consent", true)] + [InlineData("consent login", true)] + [InlineData(" consent login", true)] + [InlineData("login consent", true)] + [InlineData("login consent ", true)] + [InlineData("login consent select_account", true)] + [InlineData("login consent select_account ", true)] + [InlineData("login consent select_account ", true)] + [InlineData("login", false)] + [InlineData("login select_account", false)] + [InlineData("CONSENT", false)] + [InlineData("CONSENT LOGIN", false)] + [InlineData(" CONSENT LOGIN", false)] + [InlineData("LOGIN CONSENT", false)] + [InlineData("LOGIN CONSENT ", false)] + [InlineData("LOGIN CONSENT SELECT_ACCOUNT", false)] + [InlineData("LOGIN CONSENT SELECT_ACCOUNT ", false)] + [InlineData("LOGIN CONSENT SELECT_ACCOUNT ", false)] + [InlineData("LOGIN", false)] + [InlineData("LOGIN SELECT_ACCOUNT", false)] + public void HasPrompt_ReturnsExpectedResult(string prompt, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = new OpenIddictRequest - { - ResponseType = type - }; + Prompt = prompt + }; - // Act and assert - Assert.Equal(result, request.IsAuthorizationCodeFlow()); - } + // Act and assert + Assert.Equal(result, request.HasPrompt(Prompts.Consent)); + } - [Fact] - public void IsImplicitFlow_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsImplicitFlow()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("id_token", true)] - [InlineData("id_token ", true)] - [InlineData(" id_token", true)] - [InlineData("id_token token", true)] - [InlineData(" id_token token", true)] - [InlineData("id_token token ", true)] - [InlineData(" id_token token ", true)] - [InlineData("token", true)] - [InlineData("token ", true)] - [InlineData(" token", true)] - [InlineData("code id_token", false)] - [InlineData("code id_token token", false)] - [InlineData("code token", false)] - [InlineData("ID_TOKEN", false)] - [InlineData("ID_TOKEN ", false)] - [InlineData(" ID_TOKEN", false)] - [InlineData("ID_TOKEN TOKEN", false)] - [InlineData(" ID_TOKEN TOKEN", false)] - [InlineData("ID_TOKEN TOKEN ", false)] - [InlineData(" ID_TOKEN TOKEN ", false)] - [InlineData("TOKEN", false)] - [InlineData("TOKEN ", false)] - [InlineData(" TOKEN", false)] - [InlineData("CODE ID_TOKEN", false)] - [InlineData("CODE ID_TOKEN TOKEN", false)] - [InlineData("CODE TOKEN", false)] - public void IsImplicitFlow_ReturnsExpectedResult(string type, bool result) + [Fact] + public void HasResponseType_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; + + // Act and assert + var exception = Assert.Throws(() => { - // Arrange - var request = new OpenIddictRequest - { - ResponseType = type - }; + request.HasResponseType(ResponseTypes.Code); + }); - // Act and assert - Assert.Equal(result, request.IsImplicitFlow()); - } + Assert.Equal("request", exception.ParamName); + } - [Fact] - public void IsHybridFlow_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsHybridFlow()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("code id_token", true)] - [InlineData("code id_token ", true)] - [InlineData(" code id_token", true)] - [InlineData("code id_token token", true)] - [InlineData(" code id_token token", true)] - [InlineData("code id_token token ", true)] - [InlineData(" code id_token token ", true)] - [InlineData(" code id_token token ", true)] - [InlineData("code token", true)] - [InlineData("code token ", true)] - [InlineData(" code token", true)] - [InlineData("id_token", false)] - [InlineData("id_token token", false)] - [InlineData("token", false)] - [InlineData("CODE ID_TOKEN", false)] - [InlineData("CODE ID_TOKEN ", false)] - [InlineData(" CODE ID_TOKEN", false)] - [InlineData("CODE ID_TOKEN TOKEN", false)] - [InlineData(" CODE ID_TOKEN TOKEN", false)] - [InlineData("CODE ID_TOKEN TOKEN ", false)] - [InlineData(" CODE ID_TOKEN TOKEN ", false)] - [InlineData(" CODE ID_TOKEN TOKEN ", false)] - [InlineData("CODE TOKEN", false)] - [InlineData("CODE TOKEN ", false)] - [InlineData(" CODE TOKEN", false)] - [InlineData("ID_TOKEN", false)] - [InlineData("ID_TOKEN TOKEN", false)] - [InlineData("TOKEN", false)] - public void IsHybridFlow_ReturnsExpectedResult(string type, bool result) - { - // Arrange - var request = new OpenIddictRequest - { - ResponseType = type - }; + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasResponseType_ThrowsAnExceptionForNullOrEmptyResponseType(string type) + { + // Arrange + var request = new OpenIddictRequest(); - // Act and assert - Assert.Equal(result, request.IsHybridFlow()); - } + // Act and assert + var exception = Assert.Throws(() => request.HasResponseType(type)); - [Fact] - public void IsFragmentResponseMode_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsFragmentResponseMode()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, null, false)] - [InlineData("unknown", null, false)] - [InlineData("query", null, false)] - [InlineData("form_post", null, false)] - [InlineData("fragment", null, true)] - [InlineData("fragment ", null, false)] - [InlineData(" fragment", null, false)] - [InlineData(" fragment ", null, false)] - [InlineData(null, "code", false)] - [InlineData(null, "code id_token", true)] - [InlineData(null, "code id_token token", true)] - [InlineData(null, "code token", true)] - [InlineData(null, "id_token", true)] - [InlineData(null, "id_token token", true)] - [InlineData(null, "token", true)] - [InlineData("QUERY", null, false)] - [InlineData("FRAGMENT", null, false)] - [InlineData("FORM_POST", null, false)] - [InlineData(null, "CODE", false)] - [InlineData(null, "CODE ID_TOKEN", false)] - [InlineData(null, "CODE ID_TOKEN TOKEN", false)] - [InlineData(null, "CODE TOKEN", false)] - [InlineData(null, "ID_TOKEN", false)] - [InlineData(null, "ID_TOKEN TOKEN", false)] - [InlineData(null, "TOKEN", false)] - public void IsFragmentResponseMode_ReturnsExpectedResult(string mode, string type, bool result) + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0179), exception.Message); + } + + [Theory] + [InlineData(null, false)] + [InlineData("code", true)] + [InlineData("code id_token", true)] + [InlineData(" code id_token", true)] + [InlineData("id_token code", true)] + [InlineData("id_token code ", true)] + [InlineData("id_token code token", true)] + [InlineData("id_token code token ", true)] + [InlineData("id_token code token ", true)] + [InlineData("id_token", false)] + [InlineData("id_token token", false)] + [InlineData("CODE", false)] + [InlineData("CODE ID_TOKEN", false)] + [InlineData(" CODE ID_TOKEN", false)] + [InlineData("ID_TOKEN CODE", false)] + [InlineData("ID_TOKEN CODE ", false)] + [InlineData("ID_TOKEN CODE TOKEN", false)] + [InlineData("ID_TOKEN CODE TOKEN ", false)] + [InlineData("ID_TOKEN CODE TOKEN ", false)] + [InlineData("ID_TOKEN", false)] + [InlineData("ID_TOKEN TOKEN", false)] + public void HasResponseType_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = new OpenIddictRequest - { - ResponseMode = mode, - ResponseType = type - }; + ResponseType = type + }; - // Act and assert - Assert.Equal(result, request.IsFragmentResponseMode()); - } + // Act and assert + Assert.Equal(result, request.HasResponseType(ResponseTypes.Code)); + } - [Fact] - public void IsQueryResponseMode_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsQueryResponseMode()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, null, false)] - [InlineData("unknown", null, false)] - [InlineData("query", null, true)] - [InlineData("query ", null, false)] - [InlineData(" query", null, false)] - [InlineData(" query ", null, false)] - [InlineData("fragment", null, false)] - [InlineData("form_post", null, false)] - [InlineData(null, "none", true)] - [InlineData(null, "code", true)] - [InlineData(null, "code id_token token", false)] - [InlineData(null, "code token", false)] - [InlineData(null, "id_token", false)] - [InlineData(null, "id_token token", false)] - [InlineData(null, "token", false)] - [InlineData("QUERY", null, false)] - [InlineData("FRAGMENT", null, false)] - [InlineData("FORM_POST", null, false)] - [InlineData(null, "CODE", false)] - [InlineData(null, "CODE ID_TOKEN", false)] - [InlineData(null, "CODE ID_TOKEN TOKEN", false)] - [InlineData(null, "CODE TOKEN", false)] - [InlineData(null, "ID_TOKEN", false)] - [InlineData(null, "ID_TOKEN TOKEN", false)] - [InlineData(null, "TOKEN", false)] - public void IsQueryResponseMode_ReturnsExpectedResult(string mode, string type, bool result) + [Fact] + public void HasScope_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; + + // Act and assert + var exception = Assert.Throws(() => { - // Arrange - var request = new OpenIddictRequest - { - ResponseMode = mode, - ResponseType = type - }; + request.HasScope(Scopes.OpenId); + }); - // Act and assert - Assert.Equal(result, request.IsQueryResponseMode()); - } + Assert.Equal("request", exception.ParamName); + } - [Fact] - public void IsFormPostResponseMode_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsFormPostResponseMode()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("query", false)] - [InlineData("fragment", false)] - [InlineData("form_post", true)] - [InlineData("form_post ", false)] - [InlineData(" form_post", false)] - [InlineData(" form_post ", false)] - [InlineData("QUERY", false)] - [InlineData("FRAGMENT", false)] - [InlineData("FORM_POST", false)] - public void IsFormPostResponseMode_ReturnsExpectedResult(string mode, bool result) - { - // Arrange - var request = new OpenIddictRequest - { - ResponseMode = mode - }; + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasScope_ThrowsAnExceptionForNullOrEmptyScope(string scope) + { + // Arrange + var request = new OpenIddictRequest(); - // Act and assert - Assert.Equal(result, request.IsFormPostResponseMode()); - } + // Act and assert + var exception = Assert.Throws(() => request.HasScope(scope)); - [Fact] - public void IsAuthorizationCodeGrantType_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsAuthorizationCodeGrantType()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("authorization_code", true)] - [InlineData("authorization_code ", false)] - [InlineData(" authorization_code", false)] - [InlineData(" authorization_code ", false)] - [InlineData("client_credentials", false)] - [InlineData("password", false)] - [InlineData("refresh_token", false)] - [InlineData("AUTHORIZATION_CODE", false)] - [InlineData("CLIENT_CREDENTIALS", false)] - [InlineData("PASSWORD", false)] - [InlineData("REFRESH_TOKEN", false)] - [InlineData("urn:ietf:params:oauth:grant-type:device_code", false)] - public void IsAuthorizationCodeGrantType_ReturnsExpectedResult(string type, bool result) + Assert.Equal("scope", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0180), exception.Message); + } + + [Theory] + [InlineData(null, false)] + [InlineData("openid", true)] + [InlineData("openid ", true)] + [InlineData(" openid ", true)] + [InlineData("openid profile", true)] + [InlineData("openid profile", true)] + [InlineData("openid profile ", true)] + [InlineData(" openid profile", true)] + [InlineData("profile", false)] + [InlineData("profile email", false)] + [InlineData("OPENID", false)] + [InlineData("OPENID ", false)] + [InlineData(" OPENID ", false)] + [InlineData("OPENID PROFILE", false)] + [InlineData("OPENID PROFILE", false)] + [InlineData("OPENID PROFILE ", false)] + [InlineData(" OPENID PROFILE", false)] + [InlineData("PROFILE", false)] + [InlineData("PROFILE EMAIL", false)] + public void HasScope_ReturnsExpectedResult(string scope, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = new OpenIddictRequest - { - GrantType = type - }; + Scope = scope + }; - // Act and assert - Assert.Equal(result, request.IsAuthorizationCodeGrantType()); - } + // Act and assert + Assert.Equal(result, request.HasScope(Scopes.OpenId)); + } - [Fact] - public void IsClientCredentialsGrantType_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsClientCredentialsGrantType()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("authorization_code", false)] - [InlineData("client_credentials", true)] - [InlineData("client_credentials ", false)] - [InlineData(" client_credentials", false)] - [InlineData(" client_credentials ", false)] - [InlineData("password", false)] - [InlineData("refresh_token", false)] - [InlineData("AUTHORIZATION_CODE", false)] - [InlineData("CLIENT_CREDENTIALS", false)] - [InlineData("PASSWORD", false)] - [InlineData("REFRESH_TOKEN", false)] - [InlineData("urn:ietf:params:oauth:grant-type:device_code", false)] - public void IsClientCredentialsGrantType_ReturnsExpectedResult(string type, bool result) - { - // Arrange - var request = new OpenIddictRequest - { - GrantType = type - }; + [Fact] + public void IsNoneFlow_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - // Act and assert - Assert.Equal(result, request.IsClientCredentialsGrantType()); - } + // Act and assert + var exception = Assert.Throws(() => request.IsNoneFlow()); - [Fact] - public void IsDeviceCodeGrantType_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsDeviceCodeGrantType()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("authorization_code", false)] - [InlineData("client_credentials", false)] - [InlineData("password", false)] - [InlineData("refresh_token", false)] - [InlineData("AUTHORIZATION_CODE", false)] - [InlineData("CLIENT_CREDENTIALS", false)] - [InlineData("PASSWORD", false)] - [InlineData("REFRESH_TOKEN", false)] - [InlineData("urn:ietf:params:oauth:grant-type:device_code", true)] - [InlineData("urn:ietf:params:oauth:grant-type:device_code ", false)] - [InlineData(" urn:ietf:params:oauth:grant-type:device_code", false)] - [InlineData(" urn:ietf:params:oauth:grant-type:device_code ", false)] - public void IsDeviceCodeGrantType_ReturnsExpectedResult(string type, bool result) + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("none", true)] + [InlineData("none ", true)] + [InlineData(" none", true)] + [InlineData("none id_token", false)] + [InlineData(" none id_token", false)] + [InlineData("none id_token ", false)] + [InlineData(" none id_token ", false)] + [InlineData("NONE", false)] + [InlineData("NONE ", false)] + [InlineData(" NONE", false)] + [InlineData("NONE ID_TOKEN", false)] + [InlineData(" NONE ID_TOKEN", false)] + [InlineData("NONE ID_TOKEN ", false)] + [InlineData(" NONE ID_TOKEN ", false)] + public void IsNoneFlow_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = new OpenIddictRequest - { - GrantType = type - }; + ResponseType = type + }; - // Act and assert - Assert.Equal(result, request.IsDeviceCodeGrantType()); - } + // Act and assert + Assert.Equal(result, request.IsNoneFlow()); + } - [Fact] - public void IsPasswordGrantType_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsPasswordGrantType()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("authorization_code", false)] - [InlineData("client_credentials", false)] - [InlineData("password", true)] - [InlineData("password ", false)] - [InlineData(" password", false)] - [InlineData(" password ", false)] - [InlineData("refresh_token", false)] - [InlineData("AUTHORIZATION_CODE", false)] - [InlineData("CLIENT_CREDENTIALS", false)] - [InlineData("PASSWORD", false)] - [InlineData("REFRESH_TOKEN", false)] - [InlineData("urn:ietf:params:oauth:grant-type:device_code", false)] - public void IsPasswordGrantType_ReturnsExpectedResult(string type, bool result) - { - // Arrange - var request = new OpenIddictRequest - { - GrantType = type - }; + [Fact] + public void IsAuthorizationCodeFlow_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - // Act and assert - Assert.Equal(result, request.IsPasswordGrantType()); - } + // Act and assert + var exception = Assert.Throws(() => request.IsAuthorizationCodeFlow()); - [Fact] - public void IsRefreshTokenGrantType_ThrowsAnExceptionForNullRequest() - { - // Arrange - var request = (OpenIddictRequest) null!; - - // Act and assert - var exception = Assert.Throws(() => request.IsRefreshTokenGrantType()); - - Assert.Equal("request", exception.ParamName); - } - - [Theory] - [InlineData(null, false)] - [InlineData("unknown", false)] - [InlineData("authorization_code", false)] - [InlineData("client_credentials", false)] - [InlineData("password", false)] - [InlineData("refresh_token", true)] - [InlineData("refresh_token ", false)] - [InlineData(" refresh_token", false)] - [InlineData(" refresh_token ", false)] - [InlineData("AUTHORIZATION_CODE", false)] - [InlineData("CLIENT_CREDENTIALS", false)] - [InlineData("PASSWORD", false)] - [InlineData("REFRESH_TOKEN", false)] - [InlineData("urn:ietf:params:oauth:grant-type:device_code", false)] - public void IsRefreshTokenGrantType_ReturnsExpectedResult(string type, bool result) + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("code", true)] + [InlineData("code ", true)] + [InlineData(" code", true)] + [InlineData("code id_token", false)] + [InlineData(" code id_token", false)] + [InlineData("code id_token ", false)] + [InlineData(" code id_token ", false)] + [InlineData("CODE", false)] + [InlineData("CODE ", false)] + [InlineData(" CODE", false)] + [InlineData("CODE ID_TOKEN", false)] + [InlineData(" CODE ID_TOKEN", false)] + [InlineData("CODE ID_TOKEN ", false)] + [InlineData(" CODE ID_TOKEN ", false)] + public void IsAuthorizationCodeFlow_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var request = new OpenIddictRequest - { - GrantType = type - }; + ResponseType = type + }; + + // Act and assert + Assert.Equal(result, request.IsAuthorizationCodeFlow()); + } - // Act and assert - Assert.Equal(result, request.IsRefreshTokenGrantType()); - } + [Fact] + public void IsImplicitFlow_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - [Fact] - public void Claim_GetDestinations_ThrowsAnExceptionForNullClaim() - { - // Arrange - var claim = (Claim) null!; - - // Act and assert - var exception = Assert.Throws(() => claim.GetDestinations()); - - Assert.Equal("claim", exception.ParamName); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData("", new string[0])] - [InlineData("[]", new string[0])] - [InlineData(@"[""id_token""]", new[] { "id_token" })] - [InlineData(@"[""access_token"",""id_token""]", new[] { "access_token", "id_token" })] - [InlineData(@"[""access_token"",""access_token"",""id_token""]", new[] { "access_token", "id_token" })] - [InlineData(@"[""access_token"",""ACCESS_TOKEN"",""id_token""]", new[] { "access_token", "id_token" })] - public void Claim_GetDestinations_ReturnsExpectedDestinations(string destination, string[] destinations) + // Act and assert + var exception = Assert.Throws(() => request.IsImplicitFlow()); + + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("id_token", true)] + [InlineData("id_token ", true)] + [InlineData(" id_token", true)] + [InlineData("id_token token", true)] + [InlineData(" id_token token", true)] + [InlineData("id_token token ", true)] + [InlineData(" id_token token ", true)] + [InlineData("token", true)] + [InlineData("token ", true)] + [InlineData(" token", true)] + [InlineData("code id_token", false)] + [InlineData("code id_token token", false)] + [InlineData("code token", false)] + [InlineData("ID_TOKEN", false)] + [InlineData("ID_TOKEN ", false)] + [InlineData(" ID_TOKEN", false)] + [InlineData("ID_TOKEN TOKEN", false)] + [InlineData(" ID_TOKEN TOKEN", false)] + [InlineData("ID_TOKEN TOKEN ", false)] + [InlineData(" ID_TOKEN TOKEN ", false)] + [InlineData("TOKEN", false)] + [InlineData("TOKEN ", false)] + [InlineData(" TOKEN", false)] + [InlineData("CODE ID_TOKEN", false)] + [InlineData("CODE ID_TOKEN TOKEN", false)] + [InlineData("CODE TOKEN", false)] + public void IsImplicitFlow_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); - claim.Properties[Properties.Destinations] = destination; + ResponseType = type + }; - // Act and assert - Assert.Equal(destinations, claim.GetDestinations()); - } + // Act and assert + Assert.Equal(result, request.IsImplicitFlow()); + } + + [Fact] + public void IsHybridFlow_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - [Fact] - public void Claim_HasDestination_ThrowsAnExceptionForNullClaim() + // Act and assert + var exception = Assert.Throws(() => request.IsHybridFlow()); + + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("code id_token", true)] + [InlineData("code id_token ", true)] + [InlineData(" code id_token", true)] + [InlineData("code id_token token", true)] + [InlineData(" code id_token token", true)] + [InlineData("code id_token token ", true)] + [InlineData(" code id_token token ", true)] + [InlineData(" code id_token token ", true)] + [InlineData("code token", true)] + [InlineData("code token ", true)] + [InlineData(" code token", true)] + [InlineData("id_token", false)] + [InlineData("id_token token", false)] + [InlineData("token", false)] + [InlineData("CODE ID_TOKEN", false)] + [InlineData("CODE ID_TOKEN ", false)] + [InlineData(" CODE ID_TOKEN", false)] + [InlineData("CODE ID_TOKEN TOKEN", false)] + [InlineData(" CODE ID_TOKEN TOKEN", false)] + [InlineData("CODE ID_TOKEN TOKEN ", false)] + [InlineData(" CODE ID_TOKEN TOKEN ", false)] + [InlineData(" CODE ID_TOKEN TOKEN ", false)] + [InlineData("CODE TOKEN", false)] + [InlineData("CODE TOKEN ", false)] + [InlineData(" CODE TOKEN", false)] + [InlineData("ID_TOKEN", false)] + [InlineData("ID_TOKEN TOKEN", false)] + [InlineData("TOKEN", false)] + public void IsHybridFlow_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var claim = (Claim) null!; + ResponseType = type + }; - // Act and assert - var exception = Assert.Throws(() => claim.HasDestination("destination")); + // Act and assert + Assert.Equal(result, request.IsHybridFlow()); + } - Assert.Equal("claim", exception.ParamName); - } + [Fact] + public void IsFragmentResponseMode_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - [Fact] - public void Claim_HasDestination_ThrowsAnExceptionForNullOrEmptyDestination() + // Act and assert + var exception = Assert.Throws(() => request.IsFragmentResponseMode()); + + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, null, false)] + [InlineData("unknown", null, false)] + [InlineData("query", null, false)] + [InlineData("form_post", null, false)] + [InlineData("fragment", null, true)] + [InlineData("fragment ", null, false)] + [InlineData(" fragment", null, false)] + [InlineData(" fragment ", null, false)] + [InlineData(null, "code", false)] + [InlineData(null, "code id_token", true)] + [InlineData(null, "code id_token token", true)] + [InlineData(null, "code token", true)] + [InlineData(null, "id_token", true)] + [InlineData(null, "id_token token", true)] + [InlineData(null, "token", true)] + [InlineData("QUERY", null, false)] + [InlineData("FRAGMENT", null, false)] + [InlineData("FORM_POST", null, false)] + [InlineData(null, "CODE", false)] + [InlineData(null, "CODE ID_TOKEN", false)] + [InlineData(null, "CODE ID_TOKEN TOKEN", false)] + [InlineData(null, "CODE TOKEN", false)] + [InlineData(null, "ID_TOKEN", false)] + [InlineData(null, "ID_TOKEN TOKEN", false)] + [InlineData(null, "TOKEN", false)] + public void IsFragmentResponseMode_ReturnsExpectedResult(string mode, string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + ResponseMode = mode, + ResponseType = type + }; - // Act and assert - var exception = Assert.Throws(() => claim.HasDestination(null!)); + // Act and assert + Assert.Equal(result, request.IsFragmentResponseMode()); + } + + [Fact] + public void IsQueryResponseMode_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - Assert.Equal("destination", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0181), exception.Message); - } + // Act and assert + var exception = Assert.Throws(() => request.IsQueryResponseMode()); - [Fact] - public void Claim_HasDestination_ReturnFalseForNullOrEmptyDestinations() + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, null, false)] + [InlineData("unknown", null, false)] + [InlineData("query", null, true)] + [InlineData("query ", null, false)] + [InlineData(" query", null, false)] + [InlineData(" query ", null, false)] + [InlineData("fragment", null, false)] + [InlineData("form_post", null, false)] + [InlineData(null, "none", true)] + [InlineData(null, "code", true)] + [InlineData(null, "code id_token token", false)] + [InlineData(null, "code token", false)] + [InlineData(null, "id_token", false)] + [InlineData(null, "id_token token", false)] + [InlineData(null, "token", false)] + [InlineData("QUERY", null, false)] + [InlineData("FRAGMENT", null, false)] + [InlineData("FORM_POST", null, false)] + [InlineData(null, "CODE", false)] + [InlineData(null, "CODE ID_TOKEN", false)] + [InlineData(null, "CODE ID_TOKEN TOKEN", false)] + [InlineData(null, "CODE TOKEN", false)] + [InlineData(null, "ID_TOKEN", false)] + [InlineData(null, "ID_TOKEN TOKEN", false)] + [InlineData(null, "TOKEN", false)] + public void IsQueryResponseMode_ReturnsExpectedResult(string mode, string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + ResponseMode = mode, + ResponseType = type + }; - // Act - var hasDestination = claim.HasDestination("destination"); + // Act and assert + Assert.Equal(result, request.IsQueryResponseMode()); + } + + [Fact] + public void IsFormPostResponseMode_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - // Assert - Assert.False(hasDestination); - } + // Act and assert + var exception = Assert.Throws(() => request.IsFormPostResponseMode()); - [Fact] - public void Claim_HasDestination_ReturnTrueForExistingDestination() + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("query", false)] + [InlineData("fragment", false)] + [InlineData("form_post", true)] + [InlineData("form_post ", false)] + [InlineData(" form_post", false)] + [InlineData(" form_post ", false)] + [InlineData("QUERY", false)] + [InlineData("FRAGMENT", false)] + [InlineData("FORM_POST", false)] + public void IsFormPostResponseMode_ReturnsExpectedResult(string mode, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); - claim.SetDestinations(new[] { "destination1", "destination2", "destination3" }); + ResponseMode = mode + }; - // Act - var hasDestination = claim.HasDestination("destination2"); + // Act and assert + Assert.Equal(result, request.IsFormPostResponseMode()); + } + + [Fact] + public void IsAuthorizationCodeGrantType_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - // Assert - Assert.True(hasDestination); - } + // Act and assert + var exception = Assert.Throws(() => request.IsAuthorizationCodeGrantType()); + Assert.Equal("request", exception.ParamName); + } - [Fact] - public void Claim_SetDestinations_ThrowsAnExceptionForNullClaim() + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("authorization_code", true)] + [InlineData("authorization_code ", false)] + [InlineData(" authorization_code", false)] + [InlineData(" authorization_code ", false)] + [InlineData("client_credentials", false)] + [InlineData("password", false)] + [InlineData("refresh_token", false)] + [InlineData("AUTHORIZATION_CODE", false)] + [InlineData("CLIENT_CREDENTIALS", false)] + [InlineData("PASSWORD", false)] + [InlineData("REFRESH_TOKEN", false)] + [InlineData("urn:ietf:params:oauth:grant-type:device_code", false)] + public void IsAuthorizationCodeGrantType_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var claim = (Claim) null!; + GrantType = type + }; + + // Act and assert + Assert.Equal(result, request.IsAuthorizationCodeGrantType()); + } - // Act and assert - var exception = Assert.Throws(() => claim.SetDestinations()); + [Fact] + public void IsClientCredentialsGrantType_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; - Assert.Equal("claim", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => request.IsClientCredentialsGrantType()); - [Theory] - [InlineData(null)] - [InlineData(new object[] { new string[0] })] - public void Claim_SetDestinations_RemovesPropertyForEmptyArray(string[] destinations) + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("authorization_code", false)] + [InlineData("client_credentials", true)] + [InlineData("client_credentials ", false)] + [InlineData(" client_credentials", false)] + [InlineData(" client_credentials ", false)] + [InlineData("password", false)] + [InlineData("refresh_token", false)] + [InlineData("AUTHORIZATION_CODE", false)] + [InlineData("CLIENT_CREDENTIALS", false)] + [InlineData("PASSWORD", false)] + [InlineData("REFRESH_TOKEN", false)] + [InlineData("urn:ietf:params:oauth:grant-type:device_code", false)] + public void IsClientCredentialsGrantType_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + GrantType = type + }; - // Act - claim.SetDestinations(destinations); + // Act and assert + Assert.Equal(result, request.IsClientCredentialsGrantType()); + } - // Assert - Assert.Equal(0, claim.Properties.Count); - } + [Fact] + public void IsDeviceCodeGrantType_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; + + // Act and assert + var exception = Assert.Throws(() => request.IsDeviceCodeGrantType()); + + Assert.Equal("request", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void Claim_SetDestinations_ThrowsAnExceptionForNullOrEmptyDestinations(string destination) + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("authorization_code", false)] + [InlineData("client_credentials", false)] + [InlineData("password", false)] + [InlineData("refresh_token", false)] + [InlineData("AUTHORIZATION_CODE", false)] + [InlineData("CLIENT_CREDENTIALS", false)] + [InlineData("PASSWORD", false)] + [InlineData("REFRESH_TOKEN", false)] + [InlineData("urn:ietf:params:oauth:grant-type:device_code", true)] + [InlineData("urn:ietf:params:oauth:grant-type:device_code ", false)] + [InlineData(" urn:ietf:params:oauth:grant-type:device_code", false)] + [InlineData(" urn:ietf:params:oauth:grant-type:device_code ", false)] + public void IsDeviceCodeGrantType_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); - - // Act and assert - var exception = Assert.Throws(() => claim.SetDestinations(destination)); - - Assert.Equal("destinations", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0182), exception.Message); - } - - [Theory] - [InlineData(new[] { "access_token" }, @"[""access_token""]")] - [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] - public void Claim_SetDestinations_SetsAppropriateDestinations(string[] destinations, string destination) + GrantType = type + }; + + // Act and assert + Assert.Equal(result, request.IsDeviceCodeGrantType()); + } + + [Fact] + public void IsPasswordGrantType_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; + + // Act and assert + var exception = Assert.Throws(() => request.IsPasswordGrantType()); + + Assert.Equal("request", exception.ParamName); + } + + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("authorization_code", false)] + [InlineData("client_credentials", false)] + [InlineData("password", true)] + [InlineData("password ", false)] + [InlineData(" password", false)] + [InlineData(" password ", false)] + [InlineData("refresh_token", false)] + [InlineData("AUTHORIZATION_CODE", false)] + [InlineData("CLIENT_CREDENTIALS", false)] + [InlineData("PASSWORD", false)] + [InlineData("REFRESH_TOKEN", false)] + [InlineData("urn:ietf:params:oauth:grant-type:device_code", false)] + public void IsPasswordGrantType_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + GrantType = type + }; + + // Act and assert + Assert.Equal(result, request.IsPasswordGrantType()); + } - // Act - claim.SetDestinations(destinations); + [Fact] + public void IsRefreshTokenGrantType_ThrowsAnExceptionForNullRequest() + { + // Arrange + var request = (OpenIddictRequest) null!; + + // Act and assert + var exception = Assert.Throws(() => request.IsRefreshTokenGrantType()); - // Assert - Assert.Equal(destination, claim.Properties[Properties.Destinations]); - } + Assert.Equal("request", exception.ParamName); + } - [Fact] - public void ClaimsPrincipal_GetDestinations_ThrowsAnExceptionForNullPrincipal() + [Theory] + [InlineData(null, false)] + [InlineData("unknown", false)] + [InlineData("authorization_code", false)] + [InlineData("client_credentials", false)] + [InlineData("password", false)] + [InlineData("refresh_token", true)] + [InlineData("refresh_token ", false)] + [InlineData(" refresh_token", false)] + [InlineData(" refresh_token ", false)] + [InlineData("AUTHORIZATION_CODE", false)] + [InlineData("CLIENT_CREDENTIALS", false)] + [InlineData("PASSWORD", false)] + [InlineData("REFRESH_TOKEN", false)] + [InlineData("urn:ietf:params:oauth:grant-type:device_code", false)] + public void IsRefreshTokenGrantType_ReturnsExpectedResult(string type, bool result) + { + // Arrange + var request = new OpenIddictRequest { - // Arrange - var principal = (ClaimsPrincipal) null!; + GrantType = type + }; + + // Act and assert + Assert.Equal(result, request.IsRefreshTokenGrantType()); + } + + [Fact] + public void Claim_GetDestinations_ThrowsAnExceptionForNullClaim() + { + // Arrange + var claim = (Claim) null!; + + // Act and assert + var exception = Assert.Throws(() => claim.GetDestinations()); + + Assert.Equal("claim", exception.ParamName); + } + + [Theory] + [InlineData(null, new string[0])] + [InlineData("", new string[0])] + [InlineData("[]", new string[0])] + [InlineData(@"[""id_token""]", new[] { "id_token" })] + [InlineData(@"[""access_token"",""id_token""]", new[] { "access_token", "id_token" })] + [InlineData(@"[""access_token"",""access_token"",""id_token""]", new[] { "access_token", "id_token" })] + [InlineData(@"[""access_token"",""ACCESS_TOKEN"",""id_token""]", new[] { "access_token", "id_token" })] + public void Claim_GetDestinations_ReturnsExpectedDestinations(string destination, string[] destinations) + { + // Arrange + var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + claim.Properties[Properties.Destinations] = destination; + + // Act and assert + Assert.Equal(destinations, claim.GetDestinations()); + } + + [Fact] + public void Claim_HasDestination_ThrowsAnExceptionForNullClaim() + { + // Arrange + var claim = (Claim) null!; + + // Act and assert + var exception = Assert.Throws(() => claim.HasDestination("destination")); + + Assert.Equal("claim", exception.ParamName); + } + + [Fact] + public void Claim_HasDestination_ThrowsAnExceptionForNullOrEmptyDestination() + { + // Arrange + var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + + // Act and assert + var exception = Assert.Throws(() => claim.HasDestination(null!)); + + Assert.Equal("destination", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0181), exception.Message); + } + + [Fact] + public void Claim_HasDestination_ReturnFalseForNullOrEmptyDestinations() + { + // Arrange + var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + + // Act + var hasDestination = claim.HasDestination("destination"); + + // Assert + Assert.False(hasDestination); + } + + [Fact] + public void Claim_HasDestination_ReturnTrueForExistingDestination() + { + // Arrange + var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + claim.SetDestinations(new[] { "destination1", "destination2", "destination3" }); + + // Act + var hasDestination = claim.HasDestination("destination2"); + + // Assert + Assert.True(hasDestination); + } + + + [Fact] + public void Claim_SetDestinations_ThrowsAnExceptionForNullClaim() + { + // Arrange + var claim = (Claim) null!; + + // Act and assert + var exception = Assert.Throws(() => claim.SetDestinations()); + + Assert.Equal("claim", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData(new object[] { new string[0] })] + public void Claim_SetDestinations_RemovesPropertyForEmptyArray(string[] destinations) + { + // Arrange + var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + + // Act + claim.SetDestinations(destinations); - // Act and assert - var exception = Assert.Throws(() => principal.GetDestinations()); + // Assert + Assert.Equal(0, claim.Properties.Count); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void Claim_SetDestinations_ThrowsAnExceptionForNullOrEmptyDestinations(string destination) + { + // Arrange + var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + + // Act and assert + var exception = Assert.Throws(() => claim.SetDestinations(destination)); + + Assert.Equal("destinations", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0182), exception.Message); + } + + [Theory] + [InlineData(new[] { "access_token" }, @"[""access_token""]")] + [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] + public void Claim_SetDestinations_SetsAppropriateDestinations(string[] destinations, string destination) + { + // Arrange + var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + + // Act + claim.SetDestinations(destinations); + + // Assert + Assert.Equal(destination, claim.Properties[Properties.Destinations]); + } + + [Fact] + public void ClaimsPrincipal_GetDestinations_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - Assert.Equal("principal", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => principal.GetDestinations()); - [Fact] - public void ClaimsPrincipal_GetDestinations_ReturnsExpectedDestinations() + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_GetDestinations_ReturnsExpectedDestinations() + { + // Arrange + var claims = new[] { - // Arrange - var claims = new[] + new Claim(Claims.Name, "Bob le Bricoleur") { - new Claim(Claims.Name, "Bob le Bricoleur") + Properties = { - Properties = - { - [Properties.Destinations] = @"[""access_token"",""id_token""]" - } - }, - new Claim(Claims.Email, "bob@bricoleur.com") + [Properties.Destinations] = @"[""access_token"",""id_token""]" + } + }, + new Claim(Claims.Email, "bob@bricoleur.com") + { + Properties = { - Properties = - { - [Properties.Destinations] = @"[""id_token""]" - } - }, - new Claim(Claims.Nonce, "OkjjKJkjkHJJHhgFsd") - }; - - var principal = new ClaimsPrincipal(new ClaimsIdentity(claims)); - - // Act - var destinations = principal.GetDestinations(); - - // Assert - Assert.Equal(2, destinations.Count); - Assert.Equal(new[] { Destinations.AccessToken, Destinations.IdentityToken }, destinations[Claims.Name]); - Assert.Equal(new[] { Destinations.IdentityToken }, destinations[Claims.Email]); - } - - [Fact] - public void ClaimsPrincipal_SetDestinations_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Properties.Destinations] = @"[""id_token""]" + } + }, + new Claim(Claims.Nonce, "OkjjKJkjkHJJHhgFsd") + }; - // Act and assert - var exception = Assert.Throws(() => principal.SetDestinations(destinations: null!)); + var principal = new ClaimsPrincipal(new ClaimsIdentity(claims)); - Assert.Equal("principal", exception.ParamName); - } + // Act + var destinations = principal.GetDestinations(); - [Fact] - public void ClaimsPrincipal_SetDestinations_ThrowsAnExceptionForNullDestinations() - { - // Arrange - var principal = new ClaimsPrincipal(new ClaimsIdentity()); - var destinations = (ImmutableDictionary) null!; + // Assert + Assert.Equal(2, destinations.Count); + Assert.Equal(new[] { Destinations.AccessToken, Destinations.IdentityToken }, destinations[Claims.Name]); + Assert.Equal(new[] { Destinations.IdentityToken }, destinations[Claims.Email]); + } - // Act and assert - var exception = Assert.Throws(() => principal.SetDestinations(destinations)); + [Fact] + public void ClaimsPrincipal_SetDestinations_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - Assert.Equal("destinations", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => principal.SetDestinations(destinations: null!)); - [Fact] - public void ClaimsPrincipal_SetDestinations_SetsAppropriateDestinations() - { - // Arrange - var claims = new[] - { - new Claim(Claims.Name, "Bob le Bricoleur"), - new Claim(Claims.Email, "bob@bricoleur.com"), - new Claim(Claims.Nonce, "OkjjKJkjkHJJHhgFsd") - }; - - var principal = new ClaimsPrincipal(new ClaimsIdentity(claims)); - - var destinations = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); - destinations.Add(Claims.Name, new[] { Destinations.AccessToken, Destinations.IdentityToken }); - destinations.Add(Claims.Email, new[] { Destinations.IdentityToken }); - destinations.Add(Claims.Nonce, Array.Empty()); - - // Act - principal.SetDestinations(destinations.ToImmutable()); - - // Assert - Assert.Equal(@"[""access_token"",""id_token""]", principal.FindFirst(Claims.Name)!.Properties[Properties.Destinations]); - Assert.Equal(@"[""id_token""]", principal.FindFirst(Claims.Email)!.Properties[Properties.Destinations]); - Assert.DoesNotContain(Properties.Destinations, principal.FindFirst(Claims.Nonce)!.Properties); - } - - [Theory] - [InlineData(new[] { "access_token" }, @"[""access_token""]")] - [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] - public void SetDestinations_IEnumerable_SetsAppropriateDestinations(string[] destinations, string destination) - { - // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); - - // Act - claim.SetDestinations((IEnumerable) destinations); - - // Assert - Assert.Equal(destination, claim.Properties[Properties.Destinations]); - } - - [Theory] - [InlineData(new[] { "access_token" }, @"[""access_token""]")] - [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] - public void SetDestinations_ImmutableArray_SetsAppropriateDestinations(string[] destinations, string destination) - { - // Arrange - var claim = new Claim(Claims.Name, "Bob le Bricoleur"); + Assert.Equal("principal", exception.ParamName); + } - // Act - claim.SetDestinations(ImmutableArray.Create(destinations)); + [Fact] + public void ClaimsPrincipal_SetDestinations_ThrowsAnExceptionForNullDestinations() + { + // Arrange + var principal = new ClaimsPrincipal(new ClaimsIdentity()); + var destinations = (ImmutableDictionary) null!; - // Assert - Assert.Equal(destination, claim.Properties[Properties.Destinations]); - } + // Act and assert + var exception = Assert.Throws(() => principal.SetDestinations(destinations)); - [Fact] - public void ClaimsIdentity_Clone_ReturnsDifferentInstanceWithFilteredClaims() - { - // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.AddClaim(new Claim(Claims.ClientId, "B56BF6CE-8D8C-4290-A0E7-A4F8EE0A9FC4")); - - // Act - var clone = identity.Clone(claim => claim.Type == Claims.Name); - clone.AddClaim(new Claim("clone_claim", "value")); - - // Assert - Assert.NotSame(identity, clone); - Assert.Null(identity.FindFirst("clone_claim")); - Assert.NotNull(clone.FindFirst(Claims.Name)); - Assert.Null(clone.FindFirst(Claims.ClientId)); - } - - [Fact] - public void ClaimsIdentity_Clone_ExcludesUnwantedClaims() - { - // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); - - // Act - var clone = identity.Clone(claim => claim.Type == Claims.Name); - - // Assert - Assert.Single(clone.Claims); - Assert.Null(clone.FindFirst(Claims.Subject)); - Assert.Equal("Bob le Bricoleur", clone.FindFirst(Claims.Name)!.Value); - } - - [Fact] - public void ClaimsIdentity_Clone_ExcludesUnwantedClaimsFromActor() - { - // Arrange - var identity = new ClaimsIdentity - { - Actor = new ClaimsIdentity() - }; - identity.Actor.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.Actor.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); - - // Act - var clone = identity.Clone(claim => claim.Type == Claims.Name); - - // Assert - Assert.Single(clone.Actor!.Claims); - Assert.Null(clone.Actor.FindFirst(Claims.Subject)); - Assert.Equal("Bob le Bricoleur", clone.Actor.FindFirst(Claims.Name)!.Value); - } - - [Fact] - public void ClaimsPrincipal_Clone_ExcludesUnwantedClaimsFromIdentities() + Assert.Equal("destinations", exception.ParamName); + } + + [Fact] + public void ClaimsPrincipal_SetDestinations_SetsAppropriateDestinations() + { + // Arrange + var claims = new[] { - // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); + new Claim(Claims.Name, "Bob le Bricoleur"), + new Claim(Claims.Email, "bob@bricoleur.com"), + new Claim(Claims.Nonce, "OkjjKJkjkHJJHhgFsd") + }; - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(new ClaimsIdentity(claims)); - // Act - var clone = principal.Clone(claim => claim.Type == Claims.Name); + var destinations = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); + destinations.Add(Claims.Name, new[] { Destinations.AccessToken, Destinations.IdentityToken }); + destinations.Add(Claims.Email, new[] { Destinations.IdentityToken }); + destinations.Add(Claims.Nonce, Array.Empty()); - // Assert - Assert.Single(clone.Claims); - Assert.Null(clone.FindFirst(Claims.Subject)); - Assert.Equal("Bob le Bricoleur", clone.FindFirst(Claims.Name)!.Value); - } + // Act + principal.SetDestinations(destinations.ToImmutable()); - [Fact] - public void AddClaim_ThrowsAnExceptionForNullIdentity() - { - // Arrange - var identity = (ClaimsIdentity) null!; + // Assert + Assert.Equal(@"[""access_token"",""id_token""]", principal.FindFirst(Claims.Name)!.Properties[Properties.Destinations]); + Assert.Equal(@"[""id_token""]", principal.FindFirst(Claims.Email)!.Properties[Properties.Destinations]); + Assert.DoesNotContain(Properties.Destinations, principal.FindFirst(Claims.Nonce)!.Properties); + } - // Act and assert - var exception = Assert.Throws(() => - { - identity.AddClaim(Claims.Name, "Bob le Bricoleur"); - }); + [Theory] + [InlineData(new[] { "access_token" }, @"[""access_token""]")] + [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] + public void SetDestinations_IEnumerable_SetsAppropriateDestinations(string[] destinations, string destination) + { + // Arrange + var claim = new Claim(Claims.Name, "Bob le Bricoleur"); - Assert.Equal("identity", exception.ParamName); - } + // Act + claim.SetDestinations((IEnumerable) destinations); - [Fact] - public void AddClaim_SetsAppropriateClaim() - { - // Arrange - var identity = new ClaimsIdentity(); + // Assert + Assert.Equal(destination, claim.Properties[Properties.Destinations]); + } - // Act - identity.AddClaim(Claims.Name, "Bob le Bricoleur"); + [Theory] + [InlineData(new[] { "access_token" }, @"[""access_token""]")] + [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] + public void SetDestinations_ImmutableArray_SetsAppropriateDestinations(string[] destinations, string destination) + { + // Arrange + var claim = new Claim(Claims.Name, "Bob le Bricoleur"); - // Assert - Assert.Equal("Bob le Bricoleur", identity.FindFirst(Claims.Name)!.Value); - } + // Act + claim.SetDestinations(ImmutableArray.Create(destinations)); - [Theory] - [InlineData(new[] { "access_token" }, @"[""access_token""]")] - [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] - public void AddClaim_ImmutableArray_SetsAppropriateDestinations(string[] destinations, string destination) + // Assert + Assert.Equal(destination, claim.Properties[Properties.Destinations]); + } + + [Fact] + public void ClaimsIdentity_Clone_ReturnsDifferentInstanceWithFilteredClaims() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.ClientId, "B56BF6CE-8D8C-4290-A0E7-A4F8EE0A9FC4")); + + // Act + var clone = identity.Clone(claim => claim.Type == Claims.Name); + clone.AddClaim(new Claim("clone_claim", "value")); + + // Assert + Assert.NotSame(identity, clone); + Assert.Null(identity.FindFirst("clone_claim")); + Assert.NotNull(clone.FindFirst(Claims.Name)); + Assert.Null(clone.FindFirst(Claims.ClientId)); + } + + [Fact] + public void ClaimsIdentity_Clone_ExcludesUnwantedClaims() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); + + // Act + var clone = identity.Clone(claim => claim.Type == Claims.Name); + + // Assert + Assert.Single(clone.Claims); + Assert.Null(clone.FindFirst(Claims.Subject)); + Assert.Equal("Bob le Bricoleur", clone.FindFirst(Claims.Name)!.Value); + } + + [Fact] + public void ClaimsIdentity_Clone_ExcludesUnwantedClaimsFromActor() + { + // Arrange + var identity = new ClaimsIdentity { - // Arrange - var identity = new ClaimsIdentity(); + Actor = new ClaimsIdentity() + }; + identity.Actor.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.Actor.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); - // Act - identity.AddClaim(Claims.Name, "Bob le Bricoleur", ImmutableArray.Create(destinations)); + // Act + var clone = identity.Clone(claim => claim.Type == Claims.Name); - var claim = identity.FindFirst(Claims.Name)!; + // Assert + Assert.Single(clone.Actor!.Claims); + Assert.Null(clone.Actor.FindFirst(Claims.Subject)); + Assert.Equal("Bob le Bricoleur", clone.Actor.FindFirst(Claims.Name)!.Value); + } - // Assert - Assert.Equal("Bob le Bricoleur", claim.Value); - Assert.Equal(destination, claim.Properties[Properties.Destinations]); - } + [Fact] + public void ClaimsPrincipal_Clone_ExcludesUnwantedClaimsFromIdentities() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.Subject, "D8F1A010-BD46-4F8F-AD4E-05582307F8F4")); - [Theory] - [InlineData(new[] { "access_token" }, @"[""access_token""]")] - [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] - [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] - public void AddClaim_SetsAppropriateDestinations(string[] destinations, string destination) - { - // Arrange - var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - identity.AddClaim(Claims.Name, "Bob le Bricoleur", destinations); + // Act + var clone = principal.Clone(claim => claim.Type == Claims.Name); - var claim = identity.FindFirst(Claims.Name)!; + // Assert + Assert.Single(clone.Claims); + Assert.Null(clone.FindFirst(Claims.Subject)); + Assert.Equal("Bob le Bricoleur", clone.FindFirst(Claims.Name)!.Value); + } - // Assert - Assert.Equal("Bob le Bricoleur", claim.Value); - Assert.Equal(destination, claim.Properties[Properties.Destinations]); - } + [Fact] + public void AddClaim_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; - [Fact] - public void GetClaim_ThrowsAnExceptionForNullIdentity() + // Act and assert + var exception = Assert.Throws(() => { - // Arrange - var identity = (ClaimsIdentity) null!; + identity.AddClaim(Claims.Name, "Bob le Bricoleur"); + }); - // Act and assert - var exception = Assert.Throws(() => - { - identity.GetClaim(Claims.Name); - }); + Assert.Equal("identity", exception.ParamName); + } - Assert.Equal("identity", exception.ParamName); - } + [Fact] + public void AddClaim_SetsAppropriateClaim() + { + // Arrange + var identity = new ClaimsIdentity(); - [Fact] - public void GetClaim_ReturnsNullForMissingClaims() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(); + // Act + identity.AddClaim(Claims.Name, "Bob le Bricoleur"); - // Act and assert - Assert.Null(identity.GetClaim(Claims.Name)); - Assert.Null(principal.GetClaim(Claims.Name)); - } + // Assert + Assert.Equal("Bob le Bricoleur", identity.FindFirst(Claims.Name)!.Value); + } - [Fact] - public void GetClaim_ReturnsAppropriateResult() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(new[] { "access_token" }, @"[""access_token""]")] + [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] + public void AddClaim_ImmutableArray_SetsAppropriateDestinations(string[] destinations, string destination) + { + // Arrange + var identity = new ClaimsIdentity(); - identity.AddClaim(Claims.Name, "Bob le Bricoleur"); + // Act + identity.AddClaim(Claims.Name, "Bob le Bricoleur", ImmutableArray.Create(destinations)); - // Act and assert - Assert.Equal("Bob le Bricoleur", identity.GetClaim(Claims.Name)); - Assert.Equal("Bob le Bricoleur", principal.GetClaim(Claims.Name)); - } + var claim = identity.FindFirst(Claims.Name)!; - [Fact] - public void ClaimsIdentity_Clone_ThrowsAnExceptionForNullIdentity() - { - // Arrange - var identity = (ClaimsIdentity) null!; + // Assert + Assert.Equal("Bob le Bricoleur", claim.Value); + Assert.Equal(destination, claim.Properties[Properties.Destinations]); + } - // Act and assert - var exception = Assert.Throws(() => identity.Clone(claim => true)); + [Theory] + [InlineData(new[] { "access_token" }, @"[""access_token""]")] + [InlineData(new[] { "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "access_token", "id_token" }, @"[""access_token"",""id_token""]")] + [InlineData(new[] { "access_token", "ACCESS_TOKEN", "id_token" }, @"[""access_token"",""id_token""]")] + public void AddClaim_SetsAppropriateDestinations(string[] destinations, string destination) + { + // Arrange + var identity = new ClaimsIdentity(); - Assert.Equal("identity", exception.ParamName); - } + // Act + identity.AddClaim(Claims.Name, "Bob le Bricoleur", destinations); - [Fact] - public void ClaimsIdentity_Clone_ReturnsIdenticalIdentity() - { - // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim("type", "value"); + var claim = identity.FindFirst(Claims.Name)!; - // Act - var copy = identity.Clone(claim => true); + // Assert + Assert.Equal("Bob le Bricoleur", claim.Value); + Assert.Equal(destination, claim.Properties[Properties.Destinations]); + } - // Assert - Assert.Equal("value", copy.GetClaim("type")); - Assert.Equal(identity.Claims.Count(), copy.Claims.Count()); - } + [Fact] + public void GetClaim_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; - [Fact] - public void ClaimsPrincipal_Clone_ThrowsAnExceptionForNullPrincipal() + // Act and assert + var exception = Assert.Throws(() => { - // Arrange - var principal = (ClaimsPrincipal) null!; + identity.GetClaim(Claims.Name); + }); - // Act and assert - var exception = Assert.Throws(() => principal.Clone(claim => true)); + Assert.Equal("identity", exception.ParamName); + } - Assert.Equal("principal", exception.ParamName); - } + [Fact] + public void GetClaim_ReturnsNullForMissingClaims() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(); - [Fact] - public void ClaimsPrincipal_Clone_ReturnsIdenticalPrincipal() - { - // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + // Act and assert + Assert.Null(identity.GetClaim(Claims.Name)); + Assert.Null(principal.GetClaim(Claims.Name)); + } - var principal = new ClaimsPrincipal(identity); + [Fact] + public void GetClaim_ReturnsAppropriateResult() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - var copy = principal.Clone(claim => true); + identity.AddClaim(Claims.Name, "Bob le Bricoleur"); - // Assert - Assert.Equal("Bob le Bricoleur", copy.GetClaim(Claims.Name)); - Assert.Equal(principal.Claims.Count(), copy.Claims.Count()); - } + // Act and assert + Assert.Equal("Bob le Bricoleur", identity.GetClaim(Claims.Name)); + Assert.Equal("Bob le Bricoleur", principal.GetClaim(Claims.Name)); + } - [Fact] - public void ClaimsIdentity_Clone_ReturnsDifferentIdentityInstance() - { - // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim("type", "value"); + [Fact] + public void ClaimsIdentity_Clone_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var identity = (ClaimsIdentity) null!; - // Act - var copy = identity.Clone(claim => true); - copy.AddClaim("clone_type", "value"); + // Act and assert + var exception = Assert.Throws(() => identity.Clone(claim => true)); - // Assert - Assert.NotSame(identity, copy); - Assert.Null(identity.FindFirst("clone_type")); - } + Assert.Equal("identity", exception.ParamName); + } - [Fact] - public void ClaimsPrincipal_Clone_ReturnsDifferentPrincipalInstance() - { - // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + [Fact] + public void ClaimsIdentity_Clone_ReturnsIdenticalIdentity() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); - var principal = new ClaimsPrincipal(identity); + // Act + var copy = identity.Clone(claim => true); - // Act - var copy = principal.Clone(claim => true); - copy.SetClaim("clone_claim", "value"); + // Assert + Assert.Equal("value", copy.GetClaim("type")); + Assert.Equal(identity.Claims.Count(), copy.Claims.Count()); + } - // Assert - Assert.NotSame(principal, copy); - Assert.Null(principal.FindFirst("clone_claim")); - } + [Fact] + public void ClaimsPrincipal_Clone_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void GetClaim_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.Clone(claim => true)); - // Act and assert - var exception = Assert.Throws(() => principal.GetClaim("type")); + Assert.Equal("principal", exception.ParamName); + } - Assert.Equal("principal", exception.ParamName); - } + [Fact] + public void ClaimsPrincipal_Clone_ReturnsIdenticalPrincipal() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - [Fact] - public void GetClaim_ReturnsNullForMissingClaim() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(identity); - // Act and assert - Assert.Null(principal.GetClaim("type")); - } + // Act + var copy = principal.Clone(claim => true); - [Fact] - public void GetClaim_IsCaseInsensitive() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaim("type", "value"); + // Assert + Assert.Equal("Bob le Bricoleur", copy.GetClaim(Claims.Name)); + Assert.Equal(principal.Claims.Count(), copy.Claims.Count()); + } - // Act and assert - Assert.Equal("value", principal.GetClaim("TYPE")); - } + [Fact] + public void ClaimsIdentity_Clone_ReturnsDifferentIdentityInstance() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim("type", "value"); - [Fact] - public void GetCreationDate_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act + var copy = identity.Clone(claim => true); + copy.AddClaim("clone_type", "value"); - // Act and assert - var exception = Assert.Throws(() => principal.GetCreationDate()); + // Assert + Assert.NotSame(identity, copy); + Assert.Null(identity.FindFirst("clone_type")); + } - Assert.Equal("principal", exception.ParamName); - } + [Fact] + public void ClaimsPrincipal_Clone_ReturnsDifferentPrincipalInstance() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - [Fact] - public void GetCreationDate_ReturnsNullIfNoClaim() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(identity); + + // Act + var copy = principal.Clone(claim => true); + copy.SetClaim("clone_claim", "value"); + + // Assert + Assert.NotSame(principal, copy); + Assert.Null(principal.FindFirst("clone_claim")); + } + + [Fact] + public void GetClaim_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetClaim("type")); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void GetClaim_ReturnsNullForMissingClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + + // Act and assert + Assert.Null(principal.GetClaim("type")); + } + + [Fact] + public void GetClaim_IsCaseInsensitive() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + principal.SetClaim("type", "value"); + + // Act and assert + Assert.Equal("value", principal.GetClaim("TYPE")); + } + + [Fact] + public void GetCreationDate_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetCreationDate()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void GetCreationDate_ReturnsNullIfNoClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + + // Act and assert + Assert.Null(principal.GetCreationDate()); + } + + [Fact] + public void GetCreationDate_ReturnsCreationDate() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + principal.SetClaim(Claims.Private.CreationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); + + // Act + var date = principal.GetCreationDate(); + + // Assert + Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); + } + + [Fact] + public void GetExpirationDate_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetExpirationDate()); + + Assert.Equal("principal", exception.ParamName); + } + + [Fact] + public void GetExpirationDate_ReturnsNullIfNoClaim() + { + // Arrange + var principal = new ClaimsPrincipal(); + + // Act and assert + Assert.Null(principal.GetExpirationDate()); + } + + [Fact] + public void GetExpirationDate_ReturnsExpirationDate() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + principal.SetClaim(Claims.Private.ExpirationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); + + // Act + var date = principal.GetExpirationDate(); + + // Assert + Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); + } + + [Fact] + public void GetAudiences_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetAudiences()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void GetAudiences_ReturnsExpectedAudiences(string[] audience, string[] audiences) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + + principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); + + // Act and assert + Assert.Equal(audiences, principal.GetAudiences()); + } + + [Fact] + public void GetPresenters_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetPresenters()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void GetPresenters_ReturnsExpectedPresenters(string[] presenter, string[] presenters) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + + principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); + + // Act and assert + Assert.Equal(presenters, principal.GetPresenters()); + } + + [Fact] + public void GetResources_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetResources()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void GetResources_ReturnsExpectedResources(string[] resource, string[] resources) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + + principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); + + // Act and assert + Assert.Equal(resources, principal.GetResources()); + } + + [Fact] + public void GetScopes_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetScopes()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "openid" }, new[] { "openid" })] + [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] + public void ClaimsPrincipal_GetScopes_ReturnsExpectedScopes(string[] scope, string[] scopes) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + + principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); + + // Act and assert + Assert.Equal(scopes, principal.GetScopes()); + } + + [Fact] + public void GetAccessTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; + + // Act and assert + var exception = Assert.Throws(() => principal.GetAccessTokenLifetime()); + + Assert.Equal("principal", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("62")] + public void GetAccessTokenLifetime_ReturnsExpectedResult(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); + + principal.SetClaim(Claims.Private.AccessTokenLifetime, lifetime); + + // Act and assert + Assert.Equal(ParseLifeTime(lifetime), principal.GetAccessTokenLifetime()); + } + + [Fact] + public void GetAuthorizationCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - Assert.Null(principal.GetCreationDate()); - } + // Act and assert + var exception = Assert.Throws(() => principal.GetAuthorizationCodeLifetime()); - [Fact] - public void GetCreationDate_ReturnsCreationDate() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaim(Claims.Private.CreationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); + Assert.Equal("principal", exception.ParamName); + } - // Act - var date = principal.GetCreationDate(); + [Theory] + [InlineData(null)] + [InlineData("62")] + public void GetAuthorizationCodeLifetime_ReturnsExpectedResult(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Assert - Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); - } + principal.SetClaim(Claims.Private.AuthorizationCodeLifetime, lifetime); - [Fact] - public void GetExpirationDate_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + Assert.Equal(ParseLifeTime(lifetime), principal.GetAuthorizationCodeLifetime()); + } - // Act and assert - var exception = Assert.Throws(() => principal.GetExpirationDate()); + [Fact] + public void GetDeviceCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - Assert.Equal("principal", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => principal.GetDeviceCodeLifetime()); - [Fact] - public void GetExpirationDate_ReturnsNullIfNoClaim() - { - // Arrange - var principal = new ClaimsPrincipal(); + Assert.Equal("principal", exception.ParamName); + } - // Act and assert - Assert.Null(principal.GetExpirationDate()); - } + [Theory] + [InlineData(null)] + [InlineData("62")] + public void GetDeviceCodeLifetime_ReturnsExpectedResult(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Fact] - public void GetExpirationDate_ReturnsExpirationDate() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - principal.SetClaim(Claims.Private.ExpirationDate, "Wed, 01 Jan 2020 04:30:30 GMT"); + principal.SetClaim(Claims.Private.DeviceCodeLifetime, lifetime); - // Act - var date = principal.GetExpirationDate(); + // Act and assert + Assert.Equal(ParseLifeTime(lifetime), principal.GetDeviceCodeLifetime()); + } - // Assert - Assert.Equal(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1)), date); - } + [Fact] + public void GetIdentityTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void GetAudiences_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; - - // Act and assert - var exception = Assert.Throws(() => principal.GetAudiences()); - - Assert.Equal("principal", exception.ParamName); - } - - [Theory] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void GetAudiences_ReturnsExpectedAudiences(string[] audience, string[] audiences) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => principal.GetIdentityTokenLifetime()); - principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); + Assert.Equal("principal", exception.ParamName); + } - // Act and assert - Assert.Equal(audiences, principal.GetAudiences()); - } + [Theory] + [InlineData(null)] + [InlineData("62")] + public void GetIdentityTokenLifetime_ReturnsExpectedResult(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Fact] - public void GetPresenters_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; - - // Act and assert - var exception = Assert.Throws(() => principal.GetPresenters()); - - Assert.Equal("principal", exception.ParamName); - } - - [Theory] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void GetPresenters_ReturnsExpectedPresenters(string[] presenter, string[] presenters) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + principal.SetClaim(Claims.Private.IdentityTokenLifetime, lifetime); - principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); + // Act and assert + Assert.Equal(ParseLifeTime(lifetime), principal.GetIdentityTokenLifetime()); + } - // Act and assert - Assert.Equal(presenters, principal.GetPresenters()); - } + [Fact] + public void GetRefreshTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void GetResources_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; - - // Act and assert - var exception = Assert.Throws(() => principal.GetResources()); - - Assert.Equal("principal", exception.ParamName); - } - - [Theory] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void GetResources_ReturnsExpectedResources(string[] resource, string[] resources) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => principal.GetRefreshTokenLifetime()); - principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); + Assert.Equal("principal", exception.ParamName); + } - // Act and assert - Assert.Equal(resources, principal.GetResources()); - } + [Theory] + [InlineData(null)] + [InlineData("62")] + public void GetRefreshTokenLifetime_ReturnsExpectedResult(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Fact] - public void GetScopes_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; - - // Act and assert - var exception = Assert.Throws(() => principal.GetScopes()); - - Assert.Equal("principal", exception.ParamName); - } - - [Theory] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "openid" }, new[] { "openid" })] - [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] - public void ClaimsPrincipal_GetScopes_ReturnsExpectedScopes(string[] scope, string[] scopes) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + principal.SetClaim(Claims.Private.RefreshTokenLifetime, lifetime); - principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); + // Act and assert + Assert.Equal(ParseLifeTime(lifetime), principal.GetRefreshTokenLifetime()); + } - // Act and assert - Assert.Equal(scopes, principal.GetScopes()); - } + [Fact] + public void GetUserCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void GetAccessTokenLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.GetUserCodeLifetime()); - // Act and assert - var exception = Assert.Throws(() => principal.GetAccessTokenLifetime()); + Assert.Equal("principal", exception.ParamName); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(null)] + [InlineData("62")] + public void GetUserCodeLifetime_ReturnsExpectedResult(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetAccessTokenLifetime_ReturnsExpectedResult(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + principal.SetClaim(Claims.Private.UserCodeLifetime, lifetime); - principal.SetClaim(Claims.Private.AccessTokenLifetime, lifetime); + // Act and assert + Assert.Equal(ParseLifeTime(lifetime), principal.GetUserCodeLifetime()); + } - // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetAccessTokenLifetime()); - } + [Fact] + public void GetAuthorizationId_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void GetAuthorizationCodeLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.GetAuthorizationId()); - // Act and assert - var exception = Assert.Throws(() => principal.GetAuthorizationCodeLifetime()); + Assert.Equal("principal", exception.ParamName); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(null)] + [InlineData("identifier")] + public void GetAuthorizationId_ReturnsExpectedResult(string identifier) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetAuthorizationCodeLifetime_ReturnsExpectedResult(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + principal.SetClaim(Claims.Private.AuthorizationId, identifier); - principal.SetClaim(Claims.Private.AuthorizationCodeLifetime, lifetime); + // Act and assert + Assert.Equal(identifier, principal.GetAuthorizationId()); + } - // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetAuthorizationCodeLifetime()); - } + [Fact] + public void GetTokenId_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void GetDeviceCodeLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.GetTokenId()); - // Act and assert - var exception = Assert.Throws(() => principal.GetDeviceCodeLifetime()); + Assert.Equal("principal", exception.ParamName); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(null)] + [InlineData("identifier")] + public void GetTokenId_ReturnsExpectedResult(string identifier) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetDeviceCodeLifetime_ReturnsExpectedResult(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + principal.SetClaim(Claims.Private.TokenId, identifier); - principal.SetClaim(Claims.Private.DeviceCodeLifetime, lifetime); + // Act and assert + Assert.Equal(identifier, principal.GetTokenId()); + } - // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetDeviceCodeLifetime()); - } + [Fact] + public void GetTokenType_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void GetIdentityTokenLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.GetTokenType()); - // Act and assert - var exception = Assert.Throws(() => principal.GetIdentityTokenLifetime()); + Assert.Equal("principal", exception.ParamName); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(null)] + [InlineData("access_token")] + public void GetTokenType_ReturnsExpectedResult(string type) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetIdentityTokenLifetime_ReturnsExpectedResult(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + principal.SetTokenType(type); - principal.SetClaim(Claims.Private.IdentityTokenLifetime, lifetime); + // Act and assert + Assert.Equal(type, principal.GetTokenType()); + } - // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetIdentityTokenLifetime()); - } + [Fact] + public void HasAudience_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void GetRefreshTokenLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.HasAudience("Fabrikam")); - // Act and assert - var exception = Assert.Throws(() => principal.GetRefreshTokenLifetime()); + Assert.Equal("principal", exception.ParamName); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasAudience_ThrowsAnExceptionForNullOrEmptyAudience(string audience) + { + // Arrange + var principal = new ClaimsPrincipal(); - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetRefreshTokenLifetime_ReturnsExpectedResult(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => principal.HasAudience(audience)); - principal.SetClaim(Claims.Private.RefreshTokenLifetime, lifetime); + Assert.Equal("audience", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0186), exception.Message); + } - // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetRefreshTokenLifetime()); - } + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "contoso" }, false)] + [InlineData(new[] { "contoso", "fabrikam" }, true)] + [InlineData(new[] { "fabrikam" }, true)] + [InlineData(new[] { "fabrikam", "contoso" }, true)] + [InlineData(new[] { "CONTOSO" }, false)] + [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] + public void HasAudience_ReturnsExpectedResult(string[] audience, bool result) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Fact] - public void GetUserCodeLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); - // Act and assert - var exception = Assert.Throws(() => principal.GetUserCodeLifetime()); + // Act and assert + Assert.Equal(result, principal.HasAudience("fabrikam")); + } - Assert.Equal("principal", exception.ParamName); - } + [Fact] + public void HasPresenter_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Theory] - [InlineData(null)] - [InlineData("62")] - public void GetUserCodeLifetime_ReturnsExpectedResult(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => principal.HasPresenter("Fabrikam")); - principal.SetClaim(Claims.Private.UserCodeLifetime, lifetime); + Assert.Equal("principal", exception.ParamName); + } - // Act and assert - Assert.Equal(ParseLifeTime(lifetime), principal.GetUserCodeLifetime()); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasPresenter_ThrowsAnExceptionForNullOrEmptyPresenter(string presenter) + { + // Arrange + var principal = new ClaimsPrincipal(); - [Fact] - public void GetAuthorizationId_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.HasPresenter(presenter)); - // Act and assert - var exception = Assert.Throws(() => principal.GetAuthorizationId()); + Assert.Equal("presenter", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0187), exception.Message); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "contoso" }, false)] + [InlineData(new[] { "contoso", "fabrikam" }, true)] + [InlineData(new[] { "fabrikam" }, true)] + [InlineData(new[] { "fabrikam", "contoso" }, true)] + [InlineData(new[] { "CONTOSO" }, false)] + [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] + public void HasPresenter_ReturnsExpectedResult(string[] presenter, bool result) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Theory] - [InlineData(null)] - [InlineData("identifier")] - public void GetAuthorizationId_ReturnsExpectedResult(string identifier) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); - principal.SetClaim(Claims.Private.AuthorizationId, identifier); + // Act and assert + Assert.Equal(result, principal.HasPresenter("fabrikam")); + } - // Act and assert - Assert.Equal(identifier, principal.GetAuthorizationId()); - } + [Fact] + public void HasResource_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void GetTokenId_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.HasResource("Fabrikam")); - // Act and assert - var exception = Assert.Throws(() => principal.GetTokenId()); + Assert.Equal("principal", exception.ParamName); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasResource_ThrowsAnExceptionForNullOrEmptyResource(string resource) + { + // Arrange + var principal = new ClaimsPrincipal(); - [Theory] - [InlineData(null)] - [InlineData("identifier")] - public void GetTokenId_ReturnsExpectedResult(string identifier) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => principal.HasResource(resource)); - principal.SetClaim(Claims.Private.TokenId, identifier); + Assert.Equal("resource", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0062), exception.Message); + } - // Act and assert - Assert.Equal(identifier, principal.GetTokenId()); - } + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "contoso" }, false)] + [InlineData(new[] { "contoso", "fabrikam" }, true)] + [InlineData(new[] { "fabrikam" }, true)] + [InlineData(new[] { "fabrikam", "contoso" }, true)] + [InlineData(new[] { "CONTOSO" }, false)] + [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM" }, false)] + [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] + public void HasResource_ReturnsExpectedResult(string[] resource, bool result) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Fact] - public void GetTokenType_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); - // Act and assert - var exception = Assert.Throws(() => principal.GetTokenType()); + // Act and assert + Assert.Equal(result, principal.HasResource("fabrikam")); + } - Assert.Equal("principal", exception.ParamName); - } + [Fact] + public void ClaimsPrincipal_HasScope_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Theory] - [InlineData(null)] - [InlineData("access_token")] - public void GetTokenType_ReturnsExpectedResult(string type) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => principal.HasScope(Scopes.OpenId)); - principal.SetTokenType(type); + Assert.Equal("principal", exception.ParamName); + } - // Act and assert - Assert.Equal(type, principal.GetTokenType()); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void ClaimsPrincipal_HasScope_ThrowsAnExceptionForNullOrEmptyScope(string scope) + { + // Arrange + var principal = new ClaimsPrincipal(); - [Fact] - public void HasAudience_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.HasScope(scope)); - // Act and assert - var exception = Assert.Throws(() => principal.HasAudience("Fabrikam")); + Assert.Equal("scope", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0180), exception.Message); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(new string[0], false)] + [InlineData(new[] { "profile" }, false)] + [InlineData(new[] { "profile", "openid" }, true)] + [InlineData(new[] { "openid" }, true)] + [InlineData(new[] { "openid", "profile" }, true)] + [InlineData(new[] { "PROFILE" }, false)] + [InlineData(new[] { "PROFILE", "OPENID" }, false)] + [InlineData(new[] { "OPENID" }, false)] + [InlineData(new[] { "OPENID", "PROFILE" }, false)] + public void ClaimsPrincipal_HasScope_ReturnsExpectedResult(string[] scope, bool result) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasAudience_ThrowsAnExceptionForNullOrEmptyAudience(string audience) - { - // Arrange - var principal = new ClaimsPrincipal(); - - // Act and assert - var exception = Assert.Throws(() => principal.HasAudience(audience)); - - Assert.Equal("audience", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0186), exception.Message); - } - - [Theory] - [InlineData(new string[0], false)] - [InlineData(new[] { "contoso" }, false)] - [InlineData(new[] { "contoso", "fabrikam" }, true)] - [InlineData(new[] { "fabrikam" }, true)] - [InlineData(new[] { "fabrikam", "contoso" }, true)] - [InlineData(new[] { "CONTOSO" }, false)] - [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] - public void HasAudience_ReturnsExpectedResult(string[] audience, bool result) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); - principal.SetClaims(Claims.Private.Audience, audience.ToImmutableArray()); + // Act and assert + Assert.Equal(result, principal.HasScope(Scopes.OpenId)); + } - // Act and assert - Assert.Equal(result, principal.HasAudience("fabrikam")); - } + [Fact] + public void HasTokenType_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void HasPresenter_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.HasTokenType(TokenTypeHints.AccessToken)); - // Act and assert - var exception = Assert.Throws(() => principal.HasPresenter("Fabrikam")); + Assert.Equal("principal", exception.ParamName); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasTokenType_ThrowsAnExceptionForNullOrEmptyTokenType(string type) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasPresenter_ThrowsAnExceptionForNullOrEmptyPresenter(string presenter) - { - // Arrange - var principal = new ClaimsPrincipal(); - - // Act and assert - var exception = Assert.Throws(() => principal.HasPresenter(presenter)); - - Assert.Equal("presenter", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0187), exception.Message); - } - - [Theory] - [InlineData(new string[0], false)] - [InlineData(new[] { "contoso" }, false)] - [InlineData(new[] { "contoso", "fabrikam" }, true)] - [InlineData(new[] { "fabrikam" }, true)] - [InlineData(new[] { "fabrikam", "contoso" }, true)] - [InlineData(new[] { "CONTOSO" }, false)] - [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] - public void HasPresenter_ReturnsExpectedResult(string[] presenter, bool result) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => principal.HasTokenType(type)); - principal.SetClaims(Claims.Private.Presenter, presenter.ToImmutableArray()); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0188), exception.Message); + } - // Act and assert - Assert.Equal(result, principal.HasPresenter("fabrikam")); - } + [Fact] + public void HasTokenType_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - [Fact] - public void HasResource_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + principal.SetTokenType(TokenTypeHints.AccessToken); - // Act and assert - var exception = Assert.Throws(() => principal.HasResource("Fabrikam")); + // Act and assert + Assert.True(principal.HasTokenType(TokenTypeHints.AccessToken)); + Assert.False(principal.HasTokenType(TokenTypeHints.RefreshToken)); + } - Assert.Equal("principal", exception.ParamName); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void AddClaim_ThrowsAnExceptionForNullOrEmptyType(string type) + { + // Arrange + var identity = new ClaimsIdentity(); - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasResource_ThrowsAnExceptionForNullOrEmptyResource(string resource) - { - // Arrange - var principal = new ClaimsPrincipal(); - - // Act and assert - var exception = Assert.Throws(() => principal.HasResource(resource)); - - Assert.Equal("resource", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0062), exception.Message); - } - - [Theory] - [InlineData(new string[0], false)] - [InlineData(new[] { "contoso" }, false)] - [InlineData(new[] { "contoso", "fabrikam" }, true)] - [InlineData(new[] { "fabrikam" }, true)] - [InlineData(new[] { "fabrikam", "contoso" }, true)] - [InlineData(new[] { "CONTOSO" }, false)] - [InlineData(new[] { "CONTOSO", "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM" }, false)] - [InlineData(new[] { "FABRIKAM", "CONTOSO" }, false)] - public void HasResource_ReturnsExpectedResult(string[] resource, bool result) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => identity.AddClaim(type, "value")); - principal.SetClaims(Claims.Private.Resource, resource.ToImmutableArray()); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } - // Act and assert - Assert.Equal(result, principal.HasResource("fabrikam")); - } + [Fact] + public void AddClaim_AddsExpectedClaim() + { + // Arrange + var identity = new ClaimsIdentity(); - [Fact] - public void ClaimsPrincipal_HasScope_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act + identity.AddClaim("type", "value"); - // Act and assert - var exception = Assert.Throws(() => principal.HasScope(Scopes.OpenId)); + // Assert + Assert.Equal("value", identity.GetClaim("type")); + } - Assert.Equal("principal", exception.ParamName); - } + [Fact] + public void GetClaims_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Theory] - [InlineData(null)] - [InlineData("")] - public void ClaimsPrincipal_HasScope_ThrowsAnExceptionForNullOrEmptyScope(string scope) - { - // Arrange - var principal = new ClaimsPrincipal(); - - // Act and assert - var exception = Assert.Throws(() => principal.HasScope(scope)); - - Assert.Equal("scope", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0180), exception.Message); - } - - [Theory] - [InlineData(new string[0], false)] - [InlineData(new[] { "profile" }, false)] - [InlineData(new[] { "profile", "openid" }, true)] - [InlineData(new[] { "openid" }, true)] - [InlineData(new[] { "openid", "profile" }, true)] - [InlineData(new[] { "PROFILE" }, false)] - [InlineData(new[] { "PROFILE", "OPENID" }, false)] - [InlineData(new[] { "OPENID" }, false)] - [InlineData(new[] { "OPENID", "PROFILE" }, false)] - public void ClaimsPrincipal_HasScope_ReturnsExpectedResult(string[] scope, bool result) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => principal.GetClaims("type")); - principal.SetClaims(Claims.Private.Scope, scope.ToImmutableArray()); + Assert.Equal("principal", exception.ParamName); + } - // Act and assert - Assert.Equal(result, principal.HasScope(Scopes.OpenId)); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void GetClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(); - [Fact] - public void HasTokenType_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + // Act and assert + var exception = Assert.Throws(() => principal.GetClaims(type)); - // Act and assert - var exception = Assert.Throws(() => principal.HasTokenType(TokenTypeHints.AccessToken)); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } - Assert.Equal("principal", exception.ParamName); - } + [Fact] + public void GetClaims_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.Scope, Scopes.OpenId)); + identity.AddClaim(new Claim(Claims.Scope, Scopes.Profile)); - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasTokenType_ThrowsAnExceptionForNullOrEmptyTokenType(string type) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(identity); - // Act and assert - var exception = Assert.Throws(() => principal.HasTokenType(type)); + // Act and assert + Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, principal.GetClaims(Claims.Scope)); + } - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0188), exception.Message); - } + [Fact] + public void HasClaim_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - [Fact] - public void HasTokenType_ReturnsExpectedResult() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Act and assert + var exception = Assert.Throws(() => principal.HasClaim("type")); - principal.SetTokenType(TokenTypeHints.AccessToken); + Assert.Equal("principal", exception.ParamName); + } - // Act and assert - Assert.True(principal.HasTokenType(TokenTypeHints.AccessToken)); - Assert.False(principal.HasTokenType(TokenTypeHints.RefreshToken)); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasClaim_ThrowsAnExceptionForNullOrEmptyClaimType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(); - [Theory] - [InlineData(null)] - [InlineData("")] - public void AddClaim_ThrowsAnExceptionForNullOrEmptyType(string type) - { - // Arrange - var identity = new ClaimsIdentity(); + // Act and assert + var exception = Assert.Throws(() => principal.HasClaim(type)); - // Act and assert - var exception = Assert.Throws(() => identity.AddClaim(type, "value")); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); - } + [Fact] + public void HasClaim_ReturnsExpectedResult() + { + // Arrange + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); + identity.AddClaim(new Claim(Claims.Scope, Scopes.OpenId)); + identity.AddClaim(new Claim(Claims.Scope, Scopes.Profile)); + + var principal = new ClaimsPrincipal(identity); + + // Act and assert + Assert.True(principal.HasClaim(Claims.Name)); + Assert.True(principal.HasClaim(Claims.Scope)); + Assert.False(principal.HasClaim(Claims.Nickname)); + } - [Fact] - public void AddClaim_AddsExpectedClaim() - { - // Arrange - var identity = new ClaimsIdentity(); + [Fact] + public void RemoveClaims_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act - identity.AddClaim("type", "value"); + // Act and assert + var exception = Assert.Throws(() => principal.RemoveClaims("type")); - // Assert - Assert.Equal("value", identity.GetClaim("type")); - } + Assert.Equal("principal", exception.ParamName); + } - [Fact] - public void GetClaims_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Theory] + [InlineData(null)] + [InlineData("")] + public void RemoveClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) + { + // Arrange + var principal = new ClaimsPrincipal(); - // Act and assert - var exception = Assert.Throws(() => principal.GetClaims("type")); + // Act and assert + var exception = Assert.Throws(() => principal.RemoveClaims(type)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void GetClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) - { - // Arrange - var principal = new ClaimsPrincipal(); + [Fact] + public void RemoveClaims_RemoveClaims() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act and assert - var exception = Assert.Throws(() => principal.GetClaims(type)); + principal.SetClaim("type", "value"); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); - } + // Act + principal.RemoveClaims("type"); - [Fact] - public void GetClaims_ReturnsExpectedResult() - { - // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.AddClaim(new Claim(Claims.Scope, Scopes.OpenId)); - identity.AddClaim(new Claim(Claims.Scope, Scopes.Profile)); + // Assert + Assert.Null(principal.GetClaim("type")); + } - var principal = new ClaimsPrincipal(identity); + [Fact] + public void SetClaim_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, principal.GetClaims(Claims.Scope)); - } + // Act and assert + var exception = Assert.Throws(() => principal.SetClaim("type", "value")); - [Fact] - public void HasClaim_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + Assert.Equal("principal", exception.ParamName); + } - // Act and assert - var exception = Assert.Throws(() => principal.HasClaim("type")); + [Fact] + public void SetClaim_ThrowsAnExceptionForNullIdentity() + { + // Arrange + var principal = new ClaimsPrincipal(); - Assert.Equal("principal", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => principal.SetClaim("type", "value")); - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasClaim_ThrowsAnExceptionForNullOrEmptyClaimType(string type) - { - // Arrange - var principal = new ClaimsPrincipal(); + Assert.Equal("principal", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); + } - // Act and assert - var exception = Assert.Throws(() => principal.HasClaim(type)); + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetClaim_ThrowsAnExceptionForNullOrEmptyType(string type) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); - } + // Act and assert + var exception = Assert.Throws(() => principal.SetClaim(type, "value")); - [Fact] - public void HasClaim_ReturnsExpectedResult() - { - // Arrange - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(Claims.Name, "Bob le Bricoleur")); - identity.AddClaim(new Claim(Claims.Scope, Scopes.OpenId)); - identity.AddClaim(new Claim(Claims.Scope, Scopes.Profile)); - - var principal = new ClaimsPrincipal(identity); - - // Act and assert - Assert.True(principal.HasClaim(Claims.Name)); - Assert.True(principal.HasClaim(Claims.Scope)); - Assert.False(principal.HasClaim(Claims.Nickname)); - } - - [Fact] - public void RemoveClaims_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); + } - // Act and assert - var exception = Assert.Throws(() => principal.RemoveClaims("type")); + [Fact] + public void SetClaim_AddsExpectedClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - Assert.Equal("principal", exception.ParamName); - } + // Act + principal.SetClaim("type", "value"); - [Theory] - [InlineData(null)] - [InlineData("")] - public void RemoveClaims_ThrowsAnExceptionForNullOrEmptyClaimType(string type) - { - // Arrange - var principal = new ClaimsPrincipal(); + // Assert + Assert.Equal("value", principal.GetClaim("type")); + } - // Act and assert - var exception = Assert.Throws(() => principal.RemoveClaims(type)); + [Fact] + public void SetClaim_IsCaseInsensitive() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); - } + // Act + principal.SetClaim("TYPE", "value"); - [Fact] - public void RemoveClaims_RemoveClaims() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + // Assert + Assert.Equal("value", principal.GetClaim("type")); + } - principal.SetClaim("type", "value"); + [Fact] + public void SetClaim_RemovesEmptyClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.RemoveClaims("type"); + // Act + principal.SetClaim("type", string.Empty); - // Assert - Assert.Null(principal.GetClaim("type")); - } + // Assert + Assert.Null(principal.GetClaim("type")); + } - [Fact] - public void SetClaim_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetCreationDate_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetClaim("type", "value")); + // Act and assert + var exception = Assert.Throws(() => principal.SetCreationDate(date: null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Fact] - public void SetClaim_ThrowsAnExceptionForNullIdentity() - { - // Arrange - var principal = new ClaimsPrincipal(); + [Fact] + public void SetCreationDate_AddsClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act and assert - var exception = Assert.Throws(() => principal.SetClaim("type", "value")); + // Act + principal.SetCreationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); - Assert.Equal("principal", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0286), exception.Message); - } + // Assert + Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.CreationDate)); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void SetClaim_ThrowsAnExceptionForNullOrEmptyType(string type) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Fact] + public void SetExpirationDate_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetClaim(type, "value")); + // Act and assert + var exception = Assert.Throws(() => principal.SetExpirationDate(date: null)); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0184), exception.Message); - } + Assert.Equal("principal", exception.ParamName); + } - [Fact] - public void SetClaim_AddsExpectedClaim() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Fact] + public void SetExpirationDate_AddsClaim() + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetClaim("type", "value"); + // Act + principal.SetExpirationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); - // Assert - Assert.Equal("value", principal.GetClaim("type")); - } + // Assert + Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.ExpirationDate)); + } - [Fact] - public void SetClaim_IsCaseInsensitive() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Fact] + public void SetAudiences_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act - principal.SetClaim("TYPE", "value"); + // Act and assert + var exception = Assert.Throws(() => principal.SetAudiences()); - // Assert - Assert.Equal("value", principal.GetClaim("type")); - } + Assert.Equal("principal", exception.ParamName); + } - [Fact] - public void SetClaim_RemovesEmptyClaim() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void SetAudiences_AddsAudiences(string[] audiences, string[] audience) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetClaim("type", string.Empty); + // Act + principal.SetAudiences(audiences); - // Assert - Assert.Null(principal.GetClaim("type")); - } + // Assert + Assert.Equal(audience, principal.GetClaims(Claims.Private.Audience)); + } - [Fact] - public void SetCreationDate_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetPresenters_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetCreationDate(date: null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetPresenters()); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Fact] - public void SetCreationDate_AddsClaim() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void SetPresenters_AddsPresenters(string[] presenters, string[] presenter) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetCreationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); + // Act + principal.SetPresenters(presenters); - // Assert - Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.CreationDate)); - } + // Assert + Assert.Equal(presenter, principal.GetClaims(Claims.Private.Presenter)); + } - [Fact] - public void SetExpirationDate_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetResources_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetExpirationDate(date: null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetResources()); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Fact] - public void SetExpirationDate_AddsClaim() - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] + [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] + [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] + public void SetResources_AddsResources(string[] resources, string[] resource) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetExpirationDate(new DateTimeOffset(2020, 01, 01, 05, 30, 30, TimeSpan.FromHours(1))); + // Act + principal.SetResources(resources); - // Assert - Assert.Equal("Wed, 01 Jan 2020 04:30:30 GMT", principal.GetClaim(Claims.Private.ExpirationDate)); - } + // Assert + Assert.Equal(resource, principal.GetClaims(Claims.Private.Resource)); + } - [Fact] - public void SetAudiences_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; - - // Act and assert - var exception = Assert.Throws(() => principal.SetAudiences()); - - Assert.Equal("principal", exception.ParamName); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void SetAudiences_AddsAudiences(string[] audiences, string[] audience) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Fact] + public void SetScopes_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act - principal.SetAudiences(audiences); + // Act and assert + var exception = Assert.Throws(() => principal.SetScopes()); - // Assert - Assert.Equal(audience, principal.GetClaims(Claims.Private.Audience)); - } + Assert.Equal("principal", exception.ParamName); + } - [Fact] - public void SetPresenters_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; - - // Act and assert - var exception = Assert.Throws(() => principal.SetPresenters()); - - Assert.Equal("principal", exception.ParamName); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void SetPresenters_AddsPresenters(string[] presenters, string[] presenter) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "openid" }, new[] { "openid" })] + [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] + public void SetScopes_AddsScopes(string[] scopes, string[] scope) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetPresenters(presenters); + // Act + principal.SetScopes(scopes); - // Assert - Assert.Equal(presenter, principal.GetClaims(Claims.Private.Presenter)); - } + // Assert + Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); + } - [Fact] - public void SetResources_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; - - // Act and assert - var exception = Assert.Throws(() => principal.SetResources()); - - Assert.Equal("principal", exception.ParamName); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "fabrikam" }, new[] { "fabrikam" })] - [InlineData(new[] { "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "fabrikam", "contoso" }, new[] { "fabrikam", "contoso" })] - [InlineData(new[] { "fabrikam", "FABRIKAM", "contoso" }, new[] { "fabrikam", "FABRIKAM", "contoso" })] - public void SetResources_AddsResources(string[] resources, string[] resource) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "openid" }, new[] { "openid" })] + [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] + public void SetScopes_IEnumerable_AddsScopes(string[] scopes, string[] scope) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetResources(resources); + // Act + principal.SetScopes((IEnumerable) scopes); - // Assert - Assert.Equal(resource, principal.GetClaims(Claims.Private.Resource)); - } + // Assert + Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); + } - [Fact] - public void SetScopes_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; - - // Act and assert - var exception = Assert.Throws(() => principal.SetScopes()); - - Assert.Equal("principal", exception.ParamName); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "openid" }, new[] { "openid" })] - [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] - public void SetScopes_AddsScopes(string[] scopes, string[] scope) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - // Act - principal.SetScopes(scopes); - - // Assert - Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "openid" }, new[] { "openid" })] - [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] - public void SetScopes_IEnumerable_AddsScopes(string[] scopes, string[] scope) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); - - // Act - principal.SetScopes((IEnumerable) scopes); - - // Assert - Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); - } - - [Theory] - [InlineData(null, new string[0])] - [InlineData(new string[0], new string[0])] - [InlineData(new[] { "openid" }, new[] { "openid" })] - [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] - [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] - public void SetScopes_ImmutableArray_AddsScopes(string[] scopes, string[] scope) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null, new string[0])] + [InlineData(new string[0], new string[0])] + [InlineData(new[] { "openid" }, new[] { "openid" })] + [InlineData(new[] { "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "openid", "profile" }, new[] { "openid", "profile" })] + [InlineData(new[] { "openid", "OPENID", "profile" }, new[] { "openid", "OPENID", "profile" })] + public void SetScopes_ImmutableArray_AddsScopes(string[] scopes, string[] scope) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetScopes(ImmutableArray.Create(scopes)); + // Act + principal.SetScopes(ImmutableArray.Create(scopes)); - // Assert - Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); - } + // Assert + Assert.Equal(scope, principal.GetClaims(Claims.Private.Scope)); + } - [Fact] - public void SetAccessTokenLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetAccessTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetAccessTokenLifetime(null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetAccessTokenLifetime(null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetAccessTokenLifetime_AddsLifetime(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null)] + [InlineData("62")] + public void SetAccessTokenLifetime_AddsLifetime(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetAccessTokenLifetime(ParseLifeTime(lifetime)); + // Act + principal.SetAccessTokenLifetime(ParseLifeTime(lifetime)); - // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.AccessTokenLifetime)); - } + // Assert + Assert.Equal(lifetime, principal.GetClaim(Claims.Private.AccessTokenLifetime)); + } - [Fact] - public void SetAuthorizationCodeLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetAuthorizationCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetAuthorizationCodeLifetime(null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetAuthorizationCodeLifetime(null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetAuthorizationCodeLifetime_AddsLifetime(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null)] + [InlineData("62")] + public void SetAuthorizationCodeLifetime_AddsLifetime(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetAuthorizationCodeLifetime(ParseLifeTime(lifetime)); + // Act + principal.SetAuthorizationCodeLifetime(ParseLifeTime(lifetime)); - // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.AuthorizationCodeLifetime)); - } + // Assert + Assert.Equal(lifetime, principal.GetClaim(Claims.Private.AuthorizationCodeLifetime)); + } - [Fact] - public void SetDeviceCodeLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetDeviceCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetDeviceCodeLifetime(null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetDeviceCodeLifetime(null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetDeviceCodeLifetime_AddsLifetime(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null)] + [InlineData("62")] + public void SetDeviceCodeLifetime_AddsLifetime(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetDeviceCodeLifetime(ParseLifeTime(lifetime)); + // Act + principal.SetDeviceCodeLifetime(ParseLifeTime(lifetime)); - // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.DeviceCodeLifetime)); - } + // Assert + Assert.Equal(lifetime, principal.GetClaim(Claims.Private.DeviceCodeLifetime)); + } - [Fact] - public void SetIdentityTokenLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetIdentityTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetIdentityTokenLifetime(null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetIdentityTokenLifetime(null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetIdentityTokenLifetime_AddsLifetime(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null)] + [InlineData("62")] + public void SetIdentityTokenLifetime_AddsLifetime(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetIdentityTokenLifetime(ParseLifeTime(lifetime)); + // Act + principal.SetIdentityTokenLifetime(ParseLifeTime(lifetime)); - // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.IdentityTokenLifetime)); - } + // Assert + Assert.Equal(lifetime, principal.GetClaim(Claims.Private.IdentityTokenLifetime)); + } - [Fact] - public void SetRefreshTokenLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetRefreshTokenLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetRefreshTokenLifetime(null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetRefreshTokenLifetime(null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetRefreshTokenLifetime_AddsLifetime(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null)] + [InlineData("62")] + public void SetRefreshTokenLifetime_AddsLifetime(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetRefreshTokenLifetime(ParseLifeTime(lifetime)); + // Act + principal.SetRefreshTokenLifetime(ParseLifeTime(lifetime)); - // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.RefreshTokenLifetime)); - } + // Assert + Assert.Equal(lifetime, principal.GetClaim(Claims.Private.RefreshTokenLifetime)); + } - [Fact] - public void SetUserCodeLifetime_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetUserCodeLifetime_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetUserCodeLifetime(null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetUserCodeLifetime(null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("62")] - public void SetUserCodeLifetime_AddsLifetime(string lifetime) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null)] + [InlineData("62")] + public void SetUserCodeLifetime_AddsLifetime(string lifetime) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetUserCodeLifetime(ParseLifeTime(lifetime)); + // Act + principal.SetUserCodeLifetime(ParseLifeTime(lifetime)); - // Assert - Assert.Equal(lifetime, principal.GetClaim(Claims.Private.UserCodeLifetime)); - } + // Assert + Assert.Equal(lifetime, principal.GetClaim(Claims.Private.UserCodeLifetime)); + } - [Fact] - public void SetAuthorizationId_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetAuthorizationId_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetAuthorizationId(null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetAuthorizationId(null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("identifier")] - public void SetAuthorizationId_AddsScopes(string identifier) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null)] + [InlineData("identifier")] + public void SetAuthorizationId_AddsScopes(string identifier) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetAuthorizationId(identifier); + // Act + principal.SetAuthorizationId(identifier); - // Assert - Assert.Equal(identifier, principal.GetClaim(Claims.Private.AuthorizationId)); - } + // Assert + Assert.Equal(identifier, principal.GetClaim(Claims.Private.AuthorizationId)); + } - [Fact] - public void SetTokenId_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetTokenId_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetTokenId(null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetTokenId(null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("identifier")] - public void SetTokenId_AddsScopes(string identifier) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null)] + [InlineData("identifier")] + public void SetTokenId_AddsScopes(string identifier) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetTokenId(identifier); + // Act + principal.SetTokenId(identifier); - // Assert - Assert.Equal(identifier, principal.GetClaim(Claims.Private.TokenId)); - } + // Assert + Assert.Equal(identifier, principal.GetClaim(Claims.Private.TokenId)); + } - [Fact] - public void SetTokenType_ThrowsAnExceptionForNullPrincipal() - { - // Arrange - var principal = (ClaimsPrincipal) null!; + [Fact] + public void SetTokenType_ThrowsAnExceptionForNullPrincipal() + { + // Arrange + var principal = (ClaimsPrincipal) null!; - // Act and assert - var exception = Assert.Throws(() => principal.SetTokenType(null)); + // Act and assert + var exception = Assert.Throws(() => principal.SetTokenType(null)); - Assert.Equal("principal", exception.ParamName); - } + Assert.Equal("principal", exception.ParamName); + } - [Theory] - [InlineData(null)] - [InlineData("access_token")] - public void SetTokenType_AddsType(string type) - { - // Arrange - var identity = new ClaimsIdentity(); - var principal = new ClaimsPrincipal(identity); + [Theory] + [InlineData(null)] + [InlineData("access_token")] + public void SetTokenType_AddsType(string type) + { + // Arrange + var identity = new ClaimsIdentity(); + var principal = new ClaimsPrincipal(identity); - // Act - principal.SetTokenType(type); + // Act + principal.SetTokenType(type); - // Assert - Assert.Equal(type, principal.GetClaim(Claims.Private.TokenType)); - } + // Assert + Assert.Equal(type, principal.GetClaim(Claims.Private.TokenType)); + } - private TimeSpan? ParseLifeTime(string lifetime) - { - var lifeT = lifetime is not null - ? (TimeSpan?) TimeSpan.FromSeconds(double.Parse(lifetime, NumberStyles.Number, CultureInfo.InvariantCulture)) - : null; + private TimeSpan? ParseLifeTime(string lifetime) + { + var lifeT = lifetime is not null + ? (TimeSpan?) TimeSpan.FromSeconds(double.Parse(lifetime, NumberStyles.Number, CultureInfo.InvariantCulture)) + : null; - return lifeT; - } + return lifeT; } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs index de1f3cc9..f3536bbe 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs +++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictMessageTests.cs @@ -14,429 +14,429 @@ using Xunit; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Abstractions.Tests.Primitives +namespace OpenIddict.Abstractions.Tests.Primitives; + +public class OpenIddictMessageTests { - public class OpenIddictMessageTests + [Fact] + public void Constructor_ThrowsAnExceptionForInvalidJsonElement() { - [Fact] - public void Constructor_ThrowsAnExceptionForInvalidJsonElement() + // Arrange, act and assert + var exception = Assert.Throws(delegate { - // Arrange, act and assert - var exception = Assert.Throws(delegate - { - return new OpenIddictMessage(JsonSerializer.Deserialize("[0,1,2,3]")); - }); + return new OpenIddictMessage(JsonSerializer.Deserialize("[0,1,2,3]")); + }); - Assert.Equal("parameters", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0189), exception.Message); - } + Assert.Equal("parameters", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0189), exception.Message); + } - [Fact] - public void Constructor_ThrowsAnExceptionForDuplicateParameters() + [Fact] + public void Constructor_ThrowsAnExceptionForDuplicateParameters() + { + // Arrange, act and assert + var exception = Assert.Throws(delegate { - // Arrange, act and assert - var exception = Assert.Throws(delegate + return new OpenIddictMessage(new[] { - return new OpenIddictMessage(new[] - { - new KeyValuePair("parameter", "Fabrikam"), - new KeyValuePair("parameter", "Contoso") - }); + new KeyValuePair("parameter", "Fabrikam"), + new KeyValuePair("parameter", "Contoso") }); + }); - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0191), exception.Message); - } + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0191), exception.Message); + } - [Fact] - public void Constructor_ImportsParameters() + [Fact] + public void Constructor_ImportsParameters() + { + // Arrange and act + var message = new OpenIddictMessage(new[] { - // Arrange and act - var message = new OpenIddictMessage(new[] - { - new KeyValuePair("parameter", 42) - }); + new KeyValuePair("parameter", 42) + }); - // Assert - Assert.Equal(42, (long) message.GetParameter("parameter")); - } + // Assert + Assert.Equal(42, (long) message.GetParameter("parameter")); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void Constructor_IgnoresNullOrEmptyParameterNames(string name) + [Theory] + [InlineData(null)] + [InlineData("")] + public void Constructor_IgnoresNullOrEmptyParameterNames(string name) + { + // Arrange and act + var message = new OpenIddictMessage(new[] { - // Arrange and act - var message = new OpenIddictMessage(new[] - { - new KeyValuePair(name, "Fabrikam") - }); + new KeyValuePair(name, "Fabrikam") + }); - // Assert - Assert.Equal(0, message.Count); - } + // Assert + Assert.Equal(0, message.Count); + } - [Fact] - public void Constructor_PreservesEmptyParameters() + [Fact] + public void Constructor_PreservesEmptyParameters() + { + // Arrange and act + var message = new OpenIddictMessage(new[] { - // Arrange and act - var message = new OpenIddictMessage(new[] - { - new KeyValuePair("null-parameter", (string?) null), - new KeyValuePair("empty-parameter", string.Empty) - }); + new KeyValuePair("null-parameter", (string?) null), + new KeyValuePair("empty-parameter", string.Empty) + }); - // Assert - Assert.Equal(2, message.Count); - } + // Assert + Assert.Equal(2, message.Count); + } - [Fact] - public void Constructor_CombinesDuplicateParameters() + [Fact] + public void Constructor_CombinesDuplicateParameters() + { + // Arrange and act + var message = new OpenIddictMessage(new[] { - // Arrange and act - var message = new OpenIddictMessage(new[] - { - new KeyValuePair("parameter", "Fabrikam"), - new KeyValuePair("parameter", "Contoso") - }); + new KeyValuePair("parameter", "Fabrikam"), + new KeyValuePair("parameter", "Contoso") + }); - // Assert - Assert.Equal(1, message.Count); - Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) message.GetParameter("parameter")); - } + // Assert + Assert.Equal(1, message.Count); + Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) message.GetParameter("parameter")); + } - [Fact] - public void Constructor_SupportsMultiValuedParameters() + [Fact] + public void Constructor_SupportsMultiValuedParameters() + { + // Arrange and act + var message = new OpenIddictMessage(new[] { - // Arrange and act - var message = new OpenIddictMessage(new[] - { - new KeyValuePair("parameter", new[] { "Fabrikam", "Contoso" }) - }); + new KeyValuePair("parameter", new[] { "Fabrikam", "Contoso" }) + }); - // Assert - Assert.Equal(1, message.Count); - Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) message.GetParameter("parameter")); - } + // Assert + Assert.Equal(1, message.Count); + Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) message.GetParameter("parameter")); + } - [Fact] - public void Constructor_ExtractsSingleValuedParameters() + [Fact] + public void Constructor_ExtractsSingleValuedParameters() + { + // Arrange and act + var message = new OpenIddictMessage(new[] { - // Arrange and act - var message = new OpenIddictMessage(new[] - { - new KeyValuePair("parameter", new[] { "Fabrikam" }) - }); + new KeyValuePair("parameter", new[] { "Fabrikam" }) + }); + + // Assert + Assert.Equal(1, message.Count); + Assert.Equal("Fabrikam", message.GetParameter("parameter")?.Value); + } - // Assert - Assert.Equal(1, message.Count); - Assert.Equal("Fabrikam", message.GetParameter("parameter")?.Value); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void AddParameter_ThrowsAnExceptionForNullOrEmptyName(string name) + { + // Arrange + var message = new OpenIddictMessage(); - [Theory] - [InlineData(null)] - [InlineData("")] - public void AddParameter_ThrowsAnExceptionForNullOrEmptyName(string name) + // Act and assert + var exception = Assert.Throws(() => { - // Arrange - var message = new OpenIddictMessage(); + message.AddParameter(name, new OpenIddictParameter()); + }); - // Act and assert - var exception = Assert.Throws(() => - { - message.AddParameter(name, new OpenIddictParameter()); - }); + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); + } - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); - } + [Fact] + public void AddParameter_AddsExpectedParameter() + { + // Arrange + var message = new OpenIddictMessage(); - [Fact] - public void AddParameter_AddsExpectedParameter() - { - // Arrange - var message = new OpenIddictMessage(); + // Act + message.AddParameter("parameter", 42); - // Act - message.AddParameter("parameter", 42); + // Assert + Assert.Equal(42, message.GetParameter("parameter")); + } - // Assert - Assert.Equal(42, message.GetParameter("parameter")); - } + [Fact] + public void AddParameter_IsCaseSensitive() + { + // Arrange + var message = new OpenIddictMessage(); - [Fact] - public void AddParameter_IsCaseSensitive() - { - // Arrange - var message = new OpenIddictMessage(); + // Act + message.AddParameter("PARAMETER", 42); - // Act - message.AddParameter("PARAMETER", 42); + // Assert + Assert.Null(message.GetParameter("parameter")); + } - // Assert - Assert.Null(message.GetParameter("parameter")); - } + [Fact] + public void AddParameter_PreservesEmptyParameters() + { + // Arrange + var message = new OpenIddictMessage(); + + // Act + message.AddParameter("string", string.Empty); + message.AddParameter("array", JsonSerializer.Deserialize("[]")); + message.AddParameter("object", JsonSerializer.Deserialize("{}")); + message.AddParameter("value", JsonSerializer.Deserialize( + @"{""property"":""""}").GetProperty("property").GetString()); + + // Assert + Assert.Empty((string?) message.GetParameter("string")); + Assert.NotNull((JsonElement?) message.GetParameter("array")); + Assert.NotNull((JsonElement?) message.GetParameter("object")); + Assert.NotNull((JsonElement?) message.GetParameter("value")); + } - [Fact] - public void AddParameter_PreservesEmptyParameters() - { - // Arrange - var message = new OpenIddictMessage(); - - // Act - message.AddParameter("string", string.Empty); - message.AddParameter("array", JsonSerializer.Deserialize("[]")); - message.AddParameter("object", JsonSerializer.Deserialize("{}")); - message.AddParameter("value", JsonSerializer.Deserialize( - @"{""property"":""""}").GetProperty("property").GetString()); - - // Assert - Assert.Empty((string?) message.GetParameter("string")); - Assert.NotNull((JsonElement?) message.GetParameter("array")); - Assert.NotNull((JsonElement?) message.GetParameter("object")); - Assert.NotNull((JsonElement?) message.GetParameter("value")); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void GetParameter_ThrowsAnExceptionForNullOrEmptyName(string name) - { - // Arrange - var message = new OpenIddictMessage(); + [Theory] + [InlineData(null)] + [InlineData("")] + public void GetParameter_ThrowsAnExceptionForNullOrEmptyName(string name) + { + // Arrange + var message = new OpenIddictMessage(); - // Act and assert - var exception = Assert.Throws(() => message.GetParameter(name)); + // Act and assert + var exception = Assert.Throws(() => message.GetParameter(name)); - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); - } + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); + } - [Fact] - public void GetParameter_ReturnsExpectedParameter() - { - // Arrange - var message = new OpenIddictMessage(); + [Fact] + public void GetParameter_ReturnsExpectedParameter() + { + // Arrange + var message = new OpenIddictMessage(); - message.SetParameter("parameter", 42); + message.SetParameter("parameter", 42); - // Act and assert - Assert.Equal(42, (int) message.GetParameter("parameter")); - } + // Act and assert + Assert.Equal(42, (int) message.GetParameter("parameter")); + } - [Fact] - public void GetParameter_IsCaseSensitive() - { - // Arrange - var message = new OpenIddictMessage(); + [Fact] + public void GetParameter_IsCaseSensitive() + { + // Arrange + var message = new OpenIddictMessage(); - message.SetParameter("parameter", 42); + message.SetParameter("parameter", 42); - // Act and assert - Assert.Null(message.GetParameter("PARAMETER")); - } + // Act and assert + Assert.Null(message.GetParameter("PARAMETER")); + } - [Fact] - public void GetParameter_ReturnsNullForUnsetParameter() - { - // Arrange - var message = new OpenIddictMessage(); + [Fact] + public void GetParameter_ReturnsNullForUnsetParameter() + { + // Arrange + var message = new OpenIddictMessage(); - // Act and assert - Assert.Null(message.GetParameter("parameter")); - } + // Act and assert + Assert.Null(message.GetParameter("parameter")); + } - [Fact] - public void GetParameters_EnumeratesParameters() + [Fact] + public void GetParameters_EnumeratesParameters() + { + // Arrange + var parameters = new Dictionary { - // Arrange - var parameters = new Dictionary - { - ["int"] = int.MaxValue, - ["long"] = long.MaxValue, - ["string"] = "value" - }; + ["int"] = int.MaxValue, + ["long"] = long.MaxValue, + ["string"] = "value" + }; - var message = new OpenIddictMessage(parameters); + var message = new OpenIddictMessage(parameters); - // Act and assert - Assert.Equal(parameters, message.GetParameters()); - } + // Act and assert + Assert.Equal(parameters, message.GetParameters()); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void HasParameter_ThrowsAnExceptionForNullOrEmptyName(string name) - { - // Arrange - var message = new OpenIddictMessage(); + [Theory] + [InlineData(null)] + [InlineData("")] + public void HasParameter_ThrowsAnExceptionForNullOrEmptyName(string name) + { + // Arrange + var message = new OpenIddictMessage(); - // Act and assert - var exception = Assert.Throws(() => message.HasParameter(name)); + // Act and assert + var exception = Assert.Throws(() => message.HasParameter(name)); - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); - } + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); + } - [Theory] - [InlineData("parameter", true)] - [InlineData("PARAMETER", false)] - [InlineData("missing_parameter", false)] - public void HasParameter_ReturnsExpectedResult(string parameter, bool result) - { - // Arrange - var message = new OpenIddictMessage(); + [Theory] + [InlineData("parameter", true)] + [InlineData("PARAMETER", false)] + [InlineData("missing_parameter", false)] + public void HasParameter_ReturnsExpectedResult(string parameter, bool result) + { + // Arrange + var message = new OpenIddictMessage(); - message.SetParameter("parameter", "value"); + message.SetParameter("parameter", "value"); - // Act and assert - Assert.Equal(result, message.HasParameter(parameter)); - } + // Act and assert + Assert.Equal(result, message.HasParameter(parameter)); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void RemoveParameter_ThrowsAnExceptionForNullOrEmptyName(string name) - { - // Arrange - var message = new OpenIddictMessage(); + [Theory] + [InlineData(null)] + [InlineData("")] + public void RemoveParameter_ThrowsAnExceptionForNullOrEmptyName(string name) + { + // Arrange + var message = new OpenIddictMessage(); - // Act and assert - var exception = Assert.Throws(() => message.RemoveParameter(name)); + // Act and assert + var exception = Assert.Throws(() => message.RemoveParameter(name)); - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); - } + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); + } - [Fact] - public void RemoveParameter_RemovesExpectedParameter() - { - // Arrange - var message = new OpenIddictMessage(); - message.AddParameter("parameter", 42); + [Fact] + public void RemoveParameter_RemovesExpectedParameter() + { + // Arrange + var message = new OpenIddictMessage(); + message.AddParameter("parameter", 42); - // Act - message.RemoveParameter("parameter"); + // Act + message.RemoveParameter("parameter"); - // Assert - Assert.Null(message.GetParameter("parameter")); - } + // Assert + Assert.Null(message.GetParameter("parameter")); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void SetParameter_ThrowsAnExceptionForNullOrEmptyName(string name) - { - // Arrange - var message = new OpenIddictMessage(); + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetParameter_ThrowsAnExceptionForNullOrEmptyName(string name) + { + // Arrange + var message = new OpenIddictMessage(); - // Act and assert - var exception = Assert.Throws(() => message.SetParameter(name, null)); + // Act and assert + var exception = Assert.Throws(() => message.SetParameter(name, null)); - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); - } + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); + } - [Fact] - public void SetParameter_AddsExpectedParameter() - { - // Arrange - var message = new OpenIddictMessage(); + [Fact] + public void SetParameter_AddsExpectedParameter() + { + // Arrange + var message = new OpenIddictMessage(); - // Act - message.SetParameter("parameter", 42); + // Act + message.SetParameter("parameter", 42); - // Assert - Assert.Equal(42, message.GetParameter("parameter")); - } + // Assert + Assert.Equal(42, message.GetParameter("parameter")); + } - [Fact] - public void SetParameter_IsCaseSensitive() - { - // Arrange - var message = new OpenIddictMessage(); + [Fact] + public void SetParameter_IsCaseSensitive() + { + // Arrange + var message = new OpenIddictMessage(); - // Act - message.SetParameter("PARAMETER", 42); + // Act + message.SetParameter("PARAMETER", 42); - // Assert - Assert.Null(message.GetParameter("parameter")); - } + // Assert + Assert.Null(message.GetParameter("parameter")); + } - [Fact] - public void SetParameter_RemovesNullParameters() - { - // Arrange - var message = new OpenIddictMessage(); + [Fact] + public void SetParameter_RemovesNullParameters() + { + // Arrange + var message = new OpenIddictMessage(); - // Act - message.SetParameter("null", null); + // Act + message.SetParameter("null", null); - // Assert - Assert.Empty(message.GetParameters()); - } + // Assert + Assert.Empty(message.GetParameters()); + } - [Fact] - public void SetParameter_RemovesEmptyParameters() - { - // Arrange - var message = new OpenIddictMessage(); - - // Act - message.SetParameter("string", string.Empty); - message.SetParameter("array", JsonSerializer.Deserialize("[]")); - message.SetParameter("object", JsonSerializer.Deserialize("{}")); - message.SetParameter("value", JsonSerializer.Deserialize( - @"{""property"":""""}").GetProperty("property").GetString()); - - // Assert - Assert.Empty(message.GetParameters()); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void TryGetParameter_ThrowsAnExceptionForNullOrEmptyName(string name) - { - // Arrange - var message = new OpenIddictMessage(); + [Fact] + public void SetParameter_RemovesEmptyParameters() + { + // Arrange + var message = new OpenIddictMessage(); + + // Act + message.SetParameter("string", string.Empty); + message.SetParameter("array", JsonSerializer.Deserialize("[]")); + message.SetParameter("object", JsonSerializer.Deserialize("{}")); + message.SetParameter("value", JsonSerializer.Deserialize( + @"{""property"":""""}").GetProperty("property").GetString()); + + // Assert + Assert.Empty(message.GetParameters()); + } - // Act - var exception = Assert.Throws(() => message.TryGetParameter(name, out var parameter)); + [Theory] + [InlineData(null)] + [InlineData("")] + public void TryGetParameter_ThrowsAnExceptionForNullOrEmptyName(string name) + { + // Arrange + var message = new OpenIddictMessage(); - // Assert - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); - } + // Act + var exception = Assert.Throws(() => message.TryGetParameter(name, out var parameter)); - [Fact] - public void TryGetParameter_ReturnsTrueAndExpectedParameter() - { - // Arrange - var message = new OpenIddictMessage(); - message.SetParameter("parameter", 42); + // Assert + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0190), exception.Message); + } - // Act and assert - Assert.True(message.TryGetParameter("parameter", out var parameter)); - Assert.Equal(42, (long?) parameter.Value); - } + [Fact] + public void TryGetParameter_ReturnsTrueAndExpectedParameter() + { + // Arrange + var message = new OpenIddictMessage(); + message.SetParameter("parameter", 42); - [Fact] - public void TryGetParameter_ReturnsFalseForUnsetParameter() - { - // Arrange - var message = new OpenIddictMessage(); + // Act and assert + Assert.True(message.TryGetParameter("parameter", out var parameter)); + Assert.Equal(42, (long?) parameter.Value); + } - // Act and assert - Assert.False(message.TryGetParameter("parameter", out OpenIddictParameter parameter)); - Assert.Null(parameter.Value); - } + [Fact] + public void TryGetParameter_ReturnsFalseForUnsetParameter() + { + // Arrange + var message = new OpenIddictMessage(); - [Fact] - public void ToString_ReturnsJsonRepresentation() - { - // Arrange - var message = JsonSerializer.Deserialize(@"{ + // Act and assert + Assert.False(message.TryGetParameter("parameter", out OpenIddictParameter parameter)); + Assert.Null(parameter.Value); + } + + [Fact] + public void ToString_ReturnsJsonRepresentation() + { + // Arrange + var message = JsonSerializer.Deserialize(@"{ ""redirect_uris"": [ ""https://client.example.org/callback"", ""https://client.example.org/callback2"" @@ -448,70 +448,69 @@ namespace OpenIddict.Abstractions.Tests.Primitives ""example_extension_parameter"": ""example_value"" }")!; - var options = new JsonSerializerOptions - { - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - WriteIndented = true - }; - - // Act and assert - Assert.Equal(JsonSerializer.Serialize(message, options), message.ToString()); - } - - [Theory] - [InlineData(Parameters.AccessToken)] - [InlineData(Parameters.Assertion)] - [InlineData(Parameters.ClientAssertion)] - [InlineData(Parameters.ClientSecret)] - [InlineData(Parameters.Code)] - [InlineData(Parameters.IdToken)] - [InlineData(Parameters.IdTokenHint)] - [InlineData(Parameters.Password)] - [InlineData(Parameters.RefreshToken)] - [InlineData(Parameters.Token)] - public void ToString_ExcludesSensitiveParameters(string parameter) + var options = new JsonSerializerOptions { - // Arrange - var message = new OpenIddictMessage(); - message.AddParameter(parameter, "secret value"); - - // Act and assert - var element = JsonSerializer.Deserialize(message.ToString()); - Assert.DoesNotContain("secret value", message.ToString()); - Assert.Equal("[redacted]", element.GetProperty(parameter).GetString()); - } - - [Fact] - public void WriteTo_ThrowsAnExceptionForNullWriter() - { - // Arrange - var message = new OpenIddictMessage(); + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true + }; - // Act and assert - var exception = Assert.Throws(() => message.WriteTo(writer: null!)); - Assert.Equal("writer", exception.ParamName); - } + // Act and assert + Assert.Equal(JsonSerializer.Serialize(message, options), message.ToString()); + } + + [Theory] + [InlineData(Parameters.AccessToken)] + [InlineData(Parameters.Assertion)] + [InlineData(Parameters.ClientAssertion)] + [InlineData(Parameters.ClientSecret)] + [InlineData(Parameters.Code)] + [InlineData(Parameters.IdToken)] + [InlineData(Parameters.IdTokenHint)] + [InlineData(Parameters.Password)] + [InlineData(Parameters.RefreshToken)] + [InlineData(Parameters.Token)] + public void ToString_ExcludesSensitiveParameters(string parameter) + { + // Arrange + var message = new OpenIddictMessage(); + message.AddParameter(parameter, "secret value"); + + // Act and assert + var element = JsonSerializer.Deserialize(message.ToString()); + Assert.DoesNotContain("secret value", message.ToString()); + Assert.Equal("[redacted]", element.GetProperty(parameter).GetString()); + } - [Fact] - public void WriteTo_WritesUtf8JsonRepresentation() + [Fact] + public void WriteTo_ThrowsAnExceptionForNullWriter() + { + // Arrange + var message = new OpenIddictMessage(); + + // Act and assert + var exception = Assert.Throws(() => message.WriteTo(writer: null!)); + Assert.Equal("writer", exception.ParamName); + } + + [Fact] + public void WriteTo_WritesUtf8JsonRepresentation() + { + // Arrange + var message = new OpenIddictMessage { - // Arrange - var message = new OpenIddictMessage - { - ["redirect_uris"] = new[] { "https://abc.org/callback" }, - ["client_name"] = "My Example Client" - }; + ["redirect_uris"] = new[] { "https://abc.org/callback" }, + ["client_name"] = "My Example Client" + }; - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); - // Act - message.WriteTo(writer); - writer.Flush(); + // Act + message.WriteTo(writer); + writer.Flush(); - // Assert - Assert.Equal(@"{""redirect_uris"":[""https://abc.org/callback""],""client_name"":""My Example Client""}", - Encoding.UTF8.GetString(stream.ToArray())); - } + // Assert + Assert.Equal(@"{""redirect_uris"":[""https://abc.org/callback""],""client_name"":""My Example Client""}", + Encoding.UTF8.GetString(stream.ToArray())); } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs index b91e8b90..50744551 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs +++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictParameterTests.cs @@ -13,1214 +13,1214 @@ using System.Text.Json; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Abstractions.Tests.Primitives +namespace OpenIddict.Abstractions.Tests.Primitives; + +public class OpenIddictParameterTests { - public class OpenIddictParameterTests + [Fact] + public void Count_ReturnsZeroForNullValue() { - [Fact] - public void Count_ReturnsZeroForNullValue() - { - // Arrange - var parameter = new OpenIddictParameter(); + // Arrange + var parameter = new OpenIddictParameter(); - // Act and assert - Assert.Equal(0, parameter.Count); - } + // Act and assert + Assert.Equal(0, parameter.Count); + } - [Fact] - public void Count_ReturnsZeroForBoolean() - { - // Arrange - var parameter = new OpenIddictParameter(true); + [Fact] + public void Count_ReturnsZeroForBoolean() + { + // Arrange + var parameter = new OpenIddictParameter(true); - // Act and assert - Assert.Equal(0, parameter.Count); - } + // Act and assert + Assert.Equal(0, parameter.Count); + } - [Fact] - public void Count_ReturnsZeroForLongValue() - { - // Arrange - var parameter = new OpenIddictParameter(42); + [Fact] + public void Count_ReturnsZeroForLongValue() + { + // Arrange + var parameter = new OpenIddictParameter(42); - // Act and assert - Assert.Equal(0, parameter.Count); - } + // Act and assert + Assert.Equal(0, parameter.Count); + } - [Fact] - public void Count_ReturnsZeroForString() - { - // Arrange - var parameter = new OpenIddictParameter("Fabrikam"); + [Fact] + public void Count_ReturnsZeroForString() + { + // Arrange + var parameter = new OpenIddictParameter("Fabrikam"); - // Act and assert - Assert.Equal(0, parameter.Count); - } + // Act and assert + Assert.Equal(0, parameter.Count); + } - [Fact] - public void Count_ReturnsExpectedValueForArray() - { - // Arrange - var parameter = new OpenIddictParameter(new[] - { - "Fabrikam", - "Contoso" - }); - - // Act and assert - Assert.Equal(2, parameter.Count); - } - - [Fact] - public void Count_ReturnsExpectedValueForJsonArray() + [Fact] + public void Count_ReturnsExpectedValueForArray() + { + // Arrange + var parameter = new OpenIddictParameter(new[] { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + "Fabrikam", + "Contoso" + }); - // Act and assert - Assert.Equal(2, parameter.Count); - } + // Act and assert + Assert.Equal(2, parameter.Count); + } - [Fact] - public void Count_ReturnsZeroForJsonObjects() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + [Fact] + public void Count_ReturnsExpectedValueForJsonArray() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); - // Act and assert - Assert.Equal(0, parameter.Count); - } + // Act and assert + Assert.Equal(2, parameter.Count); + } - [Fact] - public void Equals_ReturnsTrueWhenBothParametersAreNull() - { - // Arrange - var parameter = new OpenIddictParameter(); + [Fact] + public void Count_ReturnsZeroForJsonObjects() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""parameter"":""value""}")); - // Act and assert - Assert.True(parameter.Equals(new OpenIddictParameter())); - } + // Act and assert + Assert.Equal(0, parameter.Count); + } - [Fact] - public void Equals_ReturnsFalseWhenCurrentValueIsNull() - { - // Arrange - var parameter = new OpenIddictParameter(); + [Fact] + public void Equals_ReturnsTrueWhenBothParametersAreNull() + { + // Arrange + var parameter = new OpenIddictParameter(); - // Act and assert - Assert.False(parameter.Equals(new OpenIddictParameter(42))); - } + // Act and assert + Assert.True(parameter.Equals(new OpenIddictParameter())); + } - [Fact] - public void Equals_ReturnsFalseWhenOtherValueIsNull() - { - // Arrange - var parameter = new OpenIddictParameter(42); + [Fact] + public void Equals_ReturnsFalseWhenCurrentValueIsNull() + { + // Arrange + var parameter = new OpenIddictParameter(); - // Act and assert - Assert.False(parameter.Equals(new OpenIddictParameter())); - } + // Act and assert + Assert.False(parameter.Equals(new OpenIddictParameter(42))); + } - [Fact] - public void Equals_ReturnsFalseForDifferentTypes() - { - // Arrange, act and assert - Assert.False(new OpenIddictParameter(true).Equals(new OpenIddictParameter("true"))); - Assert.False(new OpenIddictParameter("true").Equals(new OpenIddictParameter(true))); + [Fact] + public void Equals_ReturnsFalseWhenOtherValueIsNull() + { + // Arrange + var parameter = new OpenIddictParameter(42); - Assert.False(new OpenIddictParameter("42").Equals(new OpenIddictParameter(42))); - Assert.False(new OpenIddictParameter(42).Equals(new OpenIddictParameter("42"))); + // Act and assert + Assert.False(parameter.Equals(new OpenIddictParameter())); + } - Assert.False(new OpenIddictParameter(JsonSerializer.Deserialize("{}")) - .Equals(new OpenIddictParameter(JsonSerializer.Deserialize("[]")))); + [Fact] + public void Equals_ReturnsFalseForDifferentTypes() + { + // Arrange, act and assert + Assert.False(new OpenIddictParameter(true).Equals(new OpenIddictParameter("true"))); + Assert.False(new OpenIddictParameter("true").Equals(new OpenIddictParameter(true))); - Assert.False(new OpenIddictParameter(JsonSerializer.Deserialize("[]")) - .Equals(new OpenIddictParameter(JsonSerializer.Deserialize("{}")))); - } + Assert.False(new OpenIddictParameter("42").Equals(new OpenIddictParameter(42))); + Assert.False(new OpenIddictParameter(42).Equals(new OpenIddictParameter("42"))); - [Fact] - public void Equals_UsesSequenceEqualForArrays() - { - // Arrange - var parameter = new OpenIddictParameter(new[] { "Fabrikam", "Contoso" }); + Assert.False(new OpenIddictParameter(JsonSerializer.Deserialize("{}")) + .Equals(new OpenIddictParameter(JsonSerializer.Deserialize("[]")))); - // Act and assert - Assert.True(parameter.Equals(new string[] { "Fabrikam", "Contoso" })); - Assert.False(parameter.Equals(new string[] { "Contoso", "Fabrikam" })); - } + Assert.False(new OpenIddictParameter(JsonSerializer.Deserialize("[]")) + .Equals(new OpenIddictParameter(JsonSerializer.Deserialize("{}")))); + } - [Fact] - public void Equals_UsesDeepEqualsForJsonArrays() - { - // Arrange - var parameter = new OpenIddictParameter(JsonSerializer.Deserialize("[0,1,2,3]")); - - // Act and assert - Assert.True(parameter.Equals(JsonSerializer.Deserialize("[0,1,2,3]"))); - Assert.False(parameter.Equals(JsonSerializer.Deserialize("[]"))); - Assert.False(parameter.Equals(JsonSerializer.Deserialize("[0,1,2]"))); - Assert.False(parameter.Equals(JsonSerializer.Deserialize("[3,2,1,0]"))); - } - - [Fact] - public void Equals_UsesDeepEqualsForJsonObjects() - { - // Arrange - var parameter = new OpenIddictParameter(JsonSerializer.Deserialize(@"{""field"":[0,1,2,3]}")); - - // Act and assert - Assert.True(parameter.Equals(JsonSerializer.Deserialize(@"{""field"":[0,1,2,3]}"))); - Assert.False(parameter.Equals(JsonSerializer.Deserialize(@"{}"))); - Assert.False(parameter.Equals(JsonSerializer.Deserialize(@"{""field"":""value""}"))); - Assert.False(parameter.Equals(JsonSerializer.Deserialize(@"{""field"":[0,1,2]}"))); - } - - [Fact] - public void Equals_ComparesUnderlyingValuesForJsonValues() - { - // Arrange - var value = JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"); - var parameter = new OpenIddictParameter(value); + [Fact] + public void Equals_UsesSequenceEqualForArrays() + { + // Arrange + var parameter = new OpenIddictParameter(new[] { "Fabrikam", "Contoso" }); - // Act and assert - Assert.True(parameter.Equals(new OpenIddictParameter(42))); - Assert.False(parameter.Equals(new OpenIddictParameter(100))); - } + // Act and assert + Assert.True(parameter.Equals(new string[] { "Fabrikam", "Contoso" })); + Assert.False(parameter.Equals(new string[] { "Contoso", "Fabrikam" })); + } - [Fact] - public void Equals_SupportsUndefinedJsonValues() - { - // Arrange - var parameter = new OpenIddictParameter(42); + [Fact] + public void Equals_UsesDeepEqualsForJsonArrays() + { + // Arrange + var parameter = new OpenIddictParameter(JsonSerializer.Deserialize("[0,1,2,3]")); + + // Act and assert + Assert.True(parameter.Equals(JsonSerializer.Deserialize("[0,1,2,3]"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize("[]"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize("[0,1,2]"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize("[3,2,1,0]"))); + } - // Act and assert - Assert.False(parameter.Equals(new OpenIddictParameter(default(JsonElement)))); - } + [Fact] + public void Equals_UsesDeepEqualsForJsonObjects() + { + // Arrange + var parameter = new OpenIddictParameter(JsonSerializer.Deserialize(@"{""field"":[0,1,2,3]}")); + + // Act and assert + Assert.True(parameter.Equals(JsonSerializer.Deserialize(@"{""field"":[0,1,2,3]}"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize(@"{}"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize(@"{""field"":""value""}"))); + Assert.False(parameter.Equals(JsonSerializer.Deserialize(@"{""field"":[0,1,2]}"))); + } - [Fact] - public void Equals_SupportsJsonValues() - { - // Arrange - var parameter = new OpenIddictParameter(42); - - // Act and assert - Assert.True(parameter.Equals(new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field")))); - Assert.False(parameter.Equals(new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":100}").GetProperty("field")))); - } - - [Fact] - public void Equals_ReturnsFalseForNonParameters() - { - // Arrange - var parameter = new OpenIddictParameter(); + [Fact] + public void Equals_ComparesUnderlyingValuesForJsonValues() + { + // Arrange + var value = JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"); + var parameter = new OpenIddictParameter(value); - // Act and assert - Assert.False(parameter.Equals(new object())); - } + // Act and assert + Assert.True(parameter.Equals(new OpenIddictParameter(42))); + Assert.False(parameter.Equals(new OpenIddictParameter(100))); + } - [Fact] - public void GetHashCode_ReturnsZeroForNullValues() - { - // Arrange - var parameter = new OpenIddictParameter(); + [Fact] + public void Equals_SupportsUndefinedJsonValues() + { + // Arrange + var parameter = new OpenIddictParameter(42); - // Act and assert - Assert.Equal(0, parameter.GetHashCode()); - } + // Act and assert + Assert.False(parameter.Equals(new OpenIddictParameter(default(JsonElement)))); + } - [Fact] - public void GetHashCode_ReturnsHashCodeValue() - { - // Arrange - var value = "Fabrikam"; - var parameter = new OpenIddictParameter(value); + [Fact] + public void Equals_SupportsJsonValues() + { + // Arrange + var parameter = new OpenIddictParameter(42); + + // Act and assert + Assert.True(parameter.Equals(new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field")))); + Assert.False(parameter.Equals(new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":100}").GetProperty("field")))); + } - // Act and assert - Assert.Equal(value.GetHashCode(), parameter.GetHashCode()); - } + [Fact] + public void Equals_ReturnsFalseForNonParameters() + { + // Arrange + var parameter = new OpenIddictParameter(); - [Fact] - public void GetHashCode_ReturnsUnderlyingJsonValueHashCode() - { - // Arrange - var value = "Fabrikam"; - var parameter = new OpenIddictParameter(JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field")); - - // Act and assert - Assert.Equal(value.GetHashCode(), parameter.GetHashCode()); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void GetNamedParameter_ThrowsAnExceptionForNullOrEmptyName(string name) - { - // Arrange - var parameter = new OpenIddictParameter(); + // Act and assert + Assert.False(parameter.Equals(new object())); + } - // Act - var exception = Assert.Throws(() => parameter.GetNamedParameter(name)); + [Fact] + public void GetHashCode_ReturnsZeroForNullValues() + { + // Arrange + var parameter = new OpenIddictParameter(); - // Assert - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0192), exception.Message); - } + // Act and assert + Assert.Equal(0, parameter.GetHashCode()); + } - [Fact] - public void GetNamedParameter_ReturnsNullForPrimitiveValues() - { - // Arrange - var parameter = new OpenIddictParameter(42); + [Fact] + public void GetHashCode_ReturnsHashCodeValue() + { + // Arrange + var value = "Fabrikam"; + var parameter = new OpenIddictParameter(value); - // Act and assert - Assert.Null(parameter.GetNamedParameter("parameter")); - } + // Act and assert + Assert.Equal(value.GetHashCode(), parameter.GetHashCode()); + } - [Fact] - public void GetNamedParameter_ReturnsNullForArrays() - { - // Arrange - var parameter = new OpenIddictParameter(new[] - { - "Fabrikam", - "Contoso" - }); - - // Act and assert - Assert.Null(parameter.GetNamedParameter("Fabrikam")); - } - - [Fact] - public void GetNamedParameter_ReturnsNullForNonexistentItem() - { - // Arrange - var parameter = new OpenIddictParameter(new JsonElement()); + [Fact] + public void GetHashCode_ReturnsUnderlyingJsonValueHashCode() + { + // Arrange + var value = "Fabrikam"; + var parameter = new OpenIddictParameter(JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field")); - // Act and assert - Assert.Null(parameter.GetNamedParameter("parameter")); - } + // Act and assert + Assert.Equal(value.GetHashCode(), parameter.GetHashCode()); + } - [Fact] - public void GetNamedParameter_ReturnsNullForJsonArrays() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + [Theory] + [InlineData(null)] + [InlineData("")] + public void GetNamedParameter_ThrowsAnExceptionForNullOrEmptyName(string name) + { + // Arrange + var parameter = new OpenIddictParameter(); - // Act and assert - Assert.Null(parameter.GetNamedParameter("Fabrikam")); - } + // Act + var exception = Assert.Throws(() => parameter.GetNamedParameter(name)); - [Fact] - public void GetNamedParameter_ReturnsExpectedParameterForJsonObject() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + // Assert + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0192), exception.Message); + } - // Act and assert - Assert.Equal("value", (string?) parameter.GetNamedParameter("parameter")); - } + [Fact] + public void GetNamedParameter_ReturnsNullForPrimitiveValues() + { + // Arrange + var parameter = new OpenIddictParameter(42); + + // Act and assert + Assert.Null(parameter.GetNamedParameter("parameter")); + } - [Fact] - public void GetUnnamedParameter_ThrowsAnExceptionForNegativeIndex() + [Fact] + public void GetNamedParameter_ReturnsNullForArrays() + { + // Arrange + var parameter = new OpenIddictParameter(new[] { - // Arrange - var parameter = new OpenIddictParameter(); + "Fabrikam", + "Contoso" + }); - // Act - var exception = Assert.Throws(() => parameter.GetUnnamedParameter(-1)); + // Act and assert + Assert.Null(parameter.GetNamedParameter("Fabrikam")); + } - // Assert - Assert.Equal("index", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0193), exception.Message); - } + [Fact] + public void GetNamedParameter_ReturnsNullForNonexistentItem() + { + // Arrange + var parameter = new OpenIddictParameter(new JsonElement()); - [Fact] - public void GetUnnamedParameter_ReturnsNullForPrimitiveValues() - { - // Arrange - var parameter = new OpenIddictParameter(42); + // Act and assert + Assert.Null(parameter.GetNamedParameter("parameter")); + } - // Act and assert - Assert.Null(parameter.GetUnnamedParameter(0)); - } + [Fact] + public void GetNamedParameter_ReturnsNullForJsonArrays() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); - [Fact] - public void GetUnnamedParameter_ReturnsNullForOutOfRangeArrayIndex() - { - // Arrange - var parameter = new OpenIddictParameter(new[] - { - "Fabrikam", - "Contoso" - }); - - // Act and assert - Assert.Null(parameter.GetUnnamedParameter(2)); - } - - [Fact] - public void GetUnnamedParameter_ReturnsExpectedNodeForArray() - { - // Arrange - var parameter = new OpenIddictParameter(new[] - { - "Fabrikam", - "Contoso" - }); - - // Act and assert - Assert.Equal("Fabrikam", (string?) parameter.GetUnnamedParameter(0)); - } - - [Fact] - public void GetUnnamedParameter_ReturnsNullForOutOfRangeJsonArrayIndex() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + // Act and assert + Assert.Null(parameter.GetNamedParameter("Fabrikam")); + } - // Act and assert - Assert.Null(parameter.GetUnnamedParameter(2)); - } + [Fact] + public void GetNamedParameter_ReturnsExpectedParameterForJsonObject() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""parameter"":""value""}")); - [Fact] - public void GetUnnamedParameter_ReturnsNullForJsonObjects() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + // Act and assert + Assert.Equal("value", (string?) parameter.GetNamedParameter("parameter")); + } - // Act and assert - Assert.Null(parameter.GetUnnamedParameter(0)); - } + [Fact] + public void GetUnnamedParameter_ThrowsAnExceptionForNegativeIndex() + { + // Arrange + var parameter = new OpenIddictParameter(); - [Fact] - public void GetUnnamedParameter_ReturnsExpectedNodeForJsonArray() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + // Act + var exception = Assert.Throws(() => parameter.GetUnnamedParameter(-1)); - // Act and assert - Assert.Equal("Fabrikam", (string?) parameter.GetUnnamedParameter(0)); - } + // Assert + Assert.Equal("index", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0193), exception.Message); + } + + [Fact] + public void GetUnnamedParameter_ReturnsNullForPrimitiveValues() + { + // Arrange + var parameter = new OpenIddictParameter(42); - [Fact] - public void GetNamedParameters_ReturnsEmptyDictionaryForPrimitiveValues() + // Act and assert + Assert.Null(parameter.GetUnnamedParameter(0)); + } + + [Fact] + public void GetUnnamedParameter_ReturnsNullForOutOfRangeArrayIndex() + { + // Arrange + var parameter = new OpenIddictParameter(new[] { - // Arrange - var parameter = new OpenIddictParameter(42); + "Fabrikam", + "Contoso" + }); - // Act and assert - Assert.Empty(parameter.GetNamedParameters()); - } + // Act and assert + Assert.Null(parameter.GetUnnamedParameter(2)); + } - [Fact] - public void GetNamedParameters_ReturnsEmptyDictionaryForArrays() + [Fact] + public void GetUnnamedParameter_ReturnsExpectedNodeForArray() + { + // Arrange + var parameter = new OpenIddictParameter(new[] { - // Arrange - var parameters = new[] - { - "Fabrikam", - "Contoso" - }; + "Fabrikam", + "Contoso" + }); - var parameter = new OpenIddictParameter(parameters); + // Act and assert + Assert.Equal("Fabrikam", (string?) parameter.GetUnnamedParameter(0)); + } - // Act and assert - Assert.Empty(parameter.GetNamedParameters()); - } + [Fact] + public void GetUnnamedParameter_ReturnsNullForOutOfRangeJsonArrayIndex() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); - [Fact] - public void GetNamedParameters_ReturnsEmptyDictionaryForJsonValues() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field")); + // Act and assert + Assert.Null(parameter.GetUnnamedParameter(2)); + } - // Act and assert - Assert.Empty(parameter.GetNamedParameters()); - } + [Fact] + public void GetUnnamedParameter_ReturnsNullForJsonObjects() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""parameter"":""value""}")); - [Fact] - public void GetNamedParameters_ReturnsEmptyDictionaryForJsonArrays() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + // Act and assert + Assert.Null(parameter.GetUnnamedParameter(0)); + } - // Act and assert - Assert.Empty(parameter.GetNamedParameters()); - } + [Fact] + public void GetUnnamedParameter_ReturnsExpectedNodeForJsonArray() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); - [Fact] - public void GetNamedParameters_ReturnsExpectedParametersForJsonObjects() - { - // Arrange - var parameters = new Dictionary - { - ["parameter"] = "value" - }; + // Act and assert + Assert.Equal("Fabrikam", (string?) parameter.GetUnnamedParameter(0)); + } - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + [Fact] + public void GetNamedParameters_ReturnsEmptyDictionaryForPrimitiveValues() + { + // Arrange + var parameter = new OpenIddictParameter(42); - // Act and assert - Assert.Equal(parameters, parameter.GetNamedParameters().ToDictionary(pair => pair.Key, pair => (string?) pair.Value)); - } + // Act and assert + Assert.Empty(parameter.GetNamedParameters()); + } - [Fact] - public void GetNamedParameters_ReturnsLastOccurrenceOfMultipleParameters() + [Fact] + public void GetNamedParameters_ReturnsEmptyDictionaryForArrays() + { + // Arrange + var parameters = new[] { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""parameter"":""value_1"",""parameter"":""value_2""}")); + "Fabrikam", + "Contoso" + }; - // Act and assert - Assert.Equal("value_2", parameter.GetNamedParameters()["parameter"]); - } + var parameter = new OpenIddictParameter(parameters); - [Fact] - public void GetUnnamedParameters_ReturnsEmptyListForPrimitiveValues() - { - // Arrange - var parameter = new OpenIddictParameter(42); + // Act and assert + Assert.Empty(parameter.GetNamedParameters()); + } - // Act and assert - Assert.Empty(parameter.GetUnnamedParameters()); - } + [Fact] + public void GetNamedParameters_ReturnsEmptyDictionaryForJsonValues() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field")); - [Fact] - public void GetUnnamedParameters_ReturnsExpectedParametersForArrays() - { - // Arrange - var parameters = new[] - { - "Fabrikam", - "Contoso" - }; - - var parameter = new OpenIddictParameter(parameters); - - // Act and assert - Assert.Equal(parameters, from element in parameter.GetUnnamedParameters() - select (string?) element); - } - - [Fact] - public void GetUnnamedParameters_ReturnsEmptyListForJsonValues() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field")); + // Act and assert + Assert.Empty(parameter.GetNamedParameters()); + } - // Act and assert - Assert.Empty(parameter.GetUnnamedParameters()); - } + [Fact] + public void GetNamedParameters_ReturnsEmptyDictionaryForJsonArrays() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); - [Fact] - public void GetUnnamedParameters_ReturnsExpectedParametersForJsonArrays() - { - // Arrange - var parameters = new[] - { - "Fabrikam", - "Contoso" - }; - - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); - - // Act and assert - Assert.Equal(parameters, from element in parameter.GetUnnamedParameters() - select (string?) element); - } - - [Fact] - public void GetUnnamedParameters_ReturnsEmptyListForJsonObjects() + // Act and assert + Assert.Empty(parameter.GetNamedParameters()); + } + + [Fact] + public void GetNamedParameters_ReturnsExpectedParametersForJsonObjects() + { + // Arrange + var parameters = new Dictionary { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + ["parameter"] = "value" + }; - // Act and assert - Assert.Empty(parameter.GetUnnamedParameters()); - } + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""parameter"":""value""}")); - [Fact] - public void IsNullOrEmpty_ReturnsTrueForNullValues() - { - // Arrange, act and assert - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((bool?) null))); - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((long?) null))); - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((string?) null))); - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((string[]?) null))); - } - - [Fact] - public void IsNullOrEmpty_ReturnsTrueForUndefinedValues() - { - // Arrange, act and assert - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(default(JsonElement)))); - } + // Act and assert + Assert.Equal(parameters, parameter.GetNamedParameters().ToDictionary(pair => pair.Key, pair => (string?) pair.Value)); + } - [Fact] - public void IsNullOrEmpty_ReturnsTrueForEmptyValues() - { - // Arrange, act and assert - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(string.Empty))); - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(Array.Empty()))); - - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( - JsonSerializer.Deserialize("[]")))); - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( - JsonSerializer.Deserialize("{}")))); - Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""""}").GetProperty("field")))); - } - - [Fact] - public void IsNullOrEmpty_ReturnsFalseForNonEmptyValues() - { - // Arrange, act and assert - Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(true))); - Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((bool?) true))); - Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(42))); - Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((long?) 42))); - Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter("Fabrikam"))); - Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new[] { "Fabrikam" }))); - - Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam""]")))); - Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}")))); - Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field")))); - } - - [Fact] - public void ToString_ReturnsEmptyStringForNullValues() - { - // Arrange - var parameter = new OpenIddictParameter(); + [Fact] + public void GetNamedParameters_ReturnsLastOccurrenceOfMultipleParameters() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""parameter"":""value_1"",""parameter"":""value_2""}")); - // Act and assert - Assert.Empty(parameter.ToString()); - } + // Act and assert + Assert.Equal("value_2", parameter.GetNamedParameters()["parameter"]); + } - [Fact] - public void ToString_ReturnsBooleanValue() - { - // Arrange, act and assert - Assert.Equal(bool.TrueString, new OpenIddictParameter(true).ToString()); - Assert.Equal(bool.FalseString, new OpenIddictParameter(false).ToString()); - } + [Fact] + public void GetUnnamedParameters_ReturnsEmptyListForPrimitiveValues() + { + // Arrange + var parameter = new OpenIddictParameter(42); + + // Act and assert + Assert.Empty(parameter.GetUnnamedParameters()); + } - [Fact] - public void ToString_ReturnsLongValue() + [Fact] + public void GetUnnamedParameters_ReturnsExpectedParametersForArrays() + { + // Arrange + var parameters = new[] { - // Arrange - var parameter = new OpenIddictParameter(42); + "Fabrikam", + "Contoso" + }; - // Act and assert - Assert.Equal("42", parameter.ToString()); - } + var parameter = new OpenIddictParameter(parameters); - [Fact] - public void ToString_ReturnsStringValue() - { - // Arrange - var parameter = new OpenIddictParameter("Fabrikam"); + // Act and assert + Assert.Equal(parameters, from element in parameter.GetUnnamedParameters() + select (string?) element); + } - // Act and assert - Assert.Equal("Fabrikam", parameter.ToString()); - } + [Fact] + public void GetUnnamedParameters_ReturnsEmptyListForJsonValues() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field")); - [Fact] - public void ToString_ReturnsSimpleRepresentationForArrays() - { - // Arrange - var parameter = new OpenIddictParameter(new[] - { - "Fabrikam", - "Contoso" - }); - - // Act and assert - Assert.Equal("Fabrikam, Contoso", parameter.ToString()); - } - - [Fact] - public void ToString_ReturnsJsonRepresentation() + // Act and assert + Assert.Empty(parameter.GetUnnamedParameters()); + } + + [Fact] + public void GetUnnamedParameters_ReturnsExpectedParametersForJsonArrays() + { + // Arrange + var parameters = new[] { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + "Fabrikam", + "Contoso" + }; - // Act and assert - Assert.Equal(@"{""parameter"":""value""}", parameter.ToString()); - } + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); - [Fact] - public void ToString_ReturnsEmptyStringForNullJsonValues() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":null}").GetProperty("field")); + // Act and assert + Assert.Equal(parameters, from element in parameter.GetUnnamedParameters() + select (string?) element); + } - // Act and assert - Assert.Empty(parameter.ToString()); - } + [Fact] + public void GetUnnamedParameters_ReturnsEmptyListForJsonObjects() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""parameter"":""value""}")); - [Fact] - public void ToString_ReturnsEmptyStringForUndefinedJsonValues() - { - // Arrange - var parameter = new OpenIddictParameter(default(JsonElement)); + // Act and assert + Assert.Empty(parameter.GetUnnamedParameters()); + } - // Act and assert - Assert.Empty(parameter.ToString()); - } + [Fact] + public void IsNullOrEmpty_ReturnsTrueForNullValues() + { + // Arrange, act and assert + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((bool?) null))); + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((long?) null))); + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((string?) null))); + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((string[]?) null))); + } - [Fact] - public void ToString_ReturnsUnderlyingJsonValue() - { - // Arrange, act and assert - Assert.Equal(bool.TrueString, new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":true}").GetProperty("field")).ToString()); - Assert.Equal(bool.FalseString, new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field")).ToString()); - Assert.Equal("Fabrikam", new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field")).ToString()); - Assert.Equal(@"[""Fabrikam"",""Contoso""]", new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":[""Fabrikam"",""Contoso""]}").GetProperty("field")).ToString()); - Assert.Equal(@"{""field"":""value""}", new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""value""}")).ToString()); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void TryGetNamedParameter_ThrowsAnExceptionForNullOrEmptyName(string name) - { - // Arrange - var parameter = new OpenIddictParameter(); + [Fact] + public void IsNullOrEmpty_ReturnsTrueForUndefinedValues() + { + // Arrange, act and assert + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(default(JsonElement)))); + } - // Act - var exception = Assert.Throws(() => parameter.TryGetNamedParameter(name, out _)); + [Fact] + public void IsNullOrEmpty_ReturnsTrueForEmptyValues() + { + // Arrange, act and assert + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(string.Empty))); + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(Array.Empty()))); + + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize("[]")))); + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize("{}")))); + Assert.True(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""""}").GetProperty("field")))); + } - // Assert - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0192), exception.Message); - } + [Fact] + public void IsNullOrEmpty_ReturnsFalseForNonEmptyValues() + { + // Arrange, act and assert + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(true))); + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((bool?) true))); + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(42))); + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter((long?) 42))); + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter("Fabrikam"))); + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter(new[] { "Fabrikam" }))); + + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam""]")))); + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}")))); + Assert.False(OpenIddictParameter.IsNullOrEmpty(new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field")))); + } - [Fact] - public void TryGetNamedParameter_ReturnsFalseForPrimitiveValues() - { - // Arrange - var parameter = new OpenIddictParameter(42); + [Fact] + public void ToString_ReturnsEmptyStringForNullValues() + { + // Arrange + var parameter = new OpenIddictParameter(); - // Act and assert - Assert.False(parameter.TryGetNamedParameter("parameter", out var value)); - Assert.Equal(default, value); - } + // Act and assert + Assert.Empty(parameter.ToString()); + } - [Fact] - public void TryGetNamedParameter_ReturnsFalseForArrays() - { - // Arrange - var parameter = new OpenIddictParameter(new[] - { - "Fabrikam", - "Contoso" - }); - - // Act and assert - Assert.False(parameter.TryGetNamedParameter("Fabrikam", out var value)); - Assert.Equal(default, value); - } - - [Fact] - public void TryGetNamedParameter_ReturnsFalseForNonexistentItem() - { - // Arrange - var parameter = new OpenIddictParameter(new JsonElement()); + [Fact] + public void ToString_ReturnsBooleanValue() + { + // Arrange, act and assert + Assert.Equal(bool.TrueString, new OpenIddictParameter(true).ToString()); + Assert.Equal(bool.FalseString, new OpenIddictParameter(false).ToString()); + } - // Act and assert - Assert.False(parameter.TryGetNamedParameter("parameter", out var value)); - Assert.Equal(default, value); - } + [Fact] + public void ToString_ReturnsLongValue() + { + // Arrange + var parameter = new OpenIddictParameter(42); - [Fact] - public void TryGetNamedParameter_ReturnsFalseForJsonArrays() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + // Act and assert + Assert.Equal("42", parameter.ToString()); + } - // Act and assert - Assert.False(parameter.TryGetNamedParameter("Fabrikam", out var value)); - Assert.Equal(default, value); - } + [Fact] + public void ToString_ReturnsStringValue() + { + // Arrange + var parameter = new OpenIddictParameter("Fabrikam"); + + // Act and assert + Assert.Equal("Fabrikam", parameter.ToString()); + } - [Fact] - public void TryGetNamedParameter_ReturnsExpectedParameterForJsonObject() + [Fact] + public void ToString_ReturnsSimpleRepresentationForArrays() + { + // Arrange + var parameter = new OpenIddictParameter(new[] { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + "Fabrikam", + "Contoso" + }); + + // Act and assert + Assert.Equal("Fabrikam, Contoso", parameter.ToString()); + } - // Act and assert - Assert.True(parameter.TryGetNamedParameter("parameter", out var value)); - Assert.Equal("value", (string?) value); - } + [Fact] + public void ToString_ReturnsJsonRepresentation() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""parameter"":""value""}")); - [Fact] - public void TryGetUnnamedParameter_ThrowsAnExceptionForNegativeIndex() - { - // Arrange - var parameter = new OpenIddictParameter(); + // Act and assert + Assert.Equal(@"{""parameter"":""value""}", parameter.ToString()); + } - // Act - var exception = Assert.Throws(() => parameter.TryGetUnnamedParameter(-1, out _)); + [Fact] + public void ToString_ReturnsEmptyStringForNullJsonValues() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":null}").GetProperty("field")); - // Assert - Assert.Equal("index", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0193), exception.Message); - } + // Act and assert + Assert.Empty(parameter.ToString()); + } - [Fact] - public void TryGetUnnamedParameter_ReturnsFalseForPrimitiveValues() - { - // Arrange - var parameter = new OpenIddictParameter(42); + [Fact] + public void ToString_ReturnsEmptyStringForUndefinedJsonValues() + { + // Arrange + var parameter = new OpenIddictParameter(default(JsonElement)); - // Act and assert - Assert.False(parameter.TryGetUnnamedParameter(0, out var value)); - Assert.Equal(default, value); - } + // Act and assert + Assert.Empty(parameter.ToString()); + } - [Fact] - public void GetParameter_ReturnsFalseForOutOfRangeArrayIndex() - { - // Arrange - var parameter = new OpenIddictParameter(new[] - { - "Fabrikam", - "Contoso" - }); - - // Act and assert - Assert.False(parameter.TryGetUnnamedParameter(2, out var value)); - Assert.Equal(default, value); - } - - [Fact] - public void TryGetUnnamedParameter_ReturnsExpectedNodeForArray() - { - // Arrange - var parameter = new OpenIddictParameter(new[] - { - "Fabrikam", - "Contoso" - }); - - // Act and assert - Assert.True(parameter.TryGetUnnamedParameter(0, out var value)); - Assert.Equal("Fabrikam", (string?) value); - } - - [Fact] - public void TryGetUnnamedParameter_ReturnsFalseForOutOfRangeJsonArrayIndex() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + [Fact] + public void ToString_ReturnsUnderlyingJsonValue() + { + // Arrange, act and assert + Assert.Equal(bool.TrueString, new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":true}").GetProperty("field")).ToString()); + Assert.Equal(bool.FalseString, new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field")).ToString()); + Assert.Equal("Fabrikam", new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field")).ToString()); + Assert.Equal(@"[""Fabrikam"",""Contoso""]", new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":[""Fabrikam"",""Contoso""]}").GetProperty("field")).ToString()); + Assert.Equal(@"{""field"":""value""}", new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""value""}")).ToString()); + } - // Act and assert - Assert.False(parameter.TryGetUnnamedParameter(2, out var value)); - Assert.Equal(default, value); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void TryGetNamedParameter_ThrowsAnExceptionForNullOrEmptyName(string name) + { + // Arrange + var parameter = new OpenIddictParameter(); - [Fact] - public void TryGetUnnamedParameter_ReturnsFalseForJsonObjects() - { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + // Act + var exception = Assert.Throws(() => parameter.TryGetNamedParameter(name, out _)); - // Act and assert - Assert.False(parameter.TryGetUnnamedParameter(0, out var value)); - Assert.Equal(default, value); - } + // Assert + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0192), exception.Message); + } - [Fact] - public void TryGetUnnamedParameter_ReturnsExpectedNodeForJsonArray() + [Fact] + public void TryGetNamedParameter_ReturnsFalseForPrimitiveValues() + { + // Arrange + var parameter = new OpenIddictParameter(42); + + // Act and assert + Assert.False(parameter.TryGetNamedParameter("parameter", out var value)); + Assert.Equal(default, value); + } + + [Fact] + public void TryGetNamedParameter_ReturnsFalseForArrays() + { + // Arrange + var parameter = new OpenIddictParameter(new[] { - // Arrange - var parameter = new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + "Fabrikam", + "Contoso" + }); - // Act and assert - Assert.True(parameter.TryGetUnnamedParameter(0, out var value)); - Assert.Equal("Fabrikam", (string?) value); - } + // Act and assert + Assert.False(parameter.TryGetNamedParameter("Fabrikam", out var value)); + Assert.Equal(default, value); + } + + [Fact] + public void TryGetNamedParameter_ReturnsFalseForNonexistentItem() + { + // Arrange + var parameter = new OpenIddictParameter(new JsonElement()); + + // Act and assert + Assert.False(parameter.TryGetNamedParameter("parameter", out var value)); + Assert.Equal(default, value); + } + + [Fact] + public void TryGetNamedParameter_ReturnsFalseForJsonArrays() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + + // Act and assert + Assert.False(parameter.TryGetNamedParameter("Fabrikam", out var value)); + Assert.Equal(default, value); + } + + [Fact] + public void TryGetNamedParameter_ReturnsExpectedParameterForJsonObject() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""parameter"":""value""}")); - [Fact] - public void WriteTo_ThrowsAnExceptionForNullWriter() + // Act and assert + Assert.True(parameter.TryGetNamedParameter("parameter", out var value)); + Assert.Equal("value", (string?) value); + } + + [Fact] + public void TryGetUnnamedParameter_ThrowsAnExceptionForNegativeIndex() + { + // Arrange + var parameter = new OpenIddictParameter(); + + // Act + var exception = Assert.Throws(() => parameter.TryGetUnnamedParameter(-1, out _)); + + // Assert + Assert.Equal("index", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0193), exception.Message); + } + + [Fact] + public void TryGetUnnamedParameter_ReturnsFalseForPrimitiveValues() + { + // Arrange + var parameter = new OpenIddictParameter(42); + + // Act and assert + Assert.False(parameter.TryGetUnnamedParameter(0, out var value)); + Assert.Equal(default, value); + } + + [Fact] + public void GetParameter_ReturnsFalseForOutOfRangeArrayIndex() + { + // Arrange + var parameter = new OpenIddictParameter(new[] { - // Arrange - var parameter = new OpenIddictParameter(); + "Fabrikam", + "Contoso" + }); - // Act and assert - var exception = Assert.Throws(() => parameter.WriteTo(writer: null!)); - Assert.Equal("writer", exception.ParamName); - } + // Act and assert + Assert.False(parameter.TryGetUnnamedParameter(2, out var value)); + Assert.Equal(default, value); + } - [Fact] - public void WriteTo_WritesUtf8JsonRepresentation() + [Fact] + public void TryGetUnnamedParameter_ReturnsExpectedNodeForArray() + { + // Arrange + var parameter = new OpenIddictParameter(new[] { - // Arrange - var parameter = new OpenIddictParameter(JsonSerializer.Deserialize(@"{ + "Fabrikam", + "Contoso" + }); + + // Act and assert + Assert.True(parameter.TryGetUnnamedParameter(0, out var value)); + Assert.Equal("Fabrikam", (string?) value); + } + + [Fact] + public void TryGetUnnamedParameter_ReturnsFalseForOutOfRangeJsonArrayIndex() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + + // Act and assert + Assert.False(parameter.TryGetUnnamedParameter(2, out var value)); + Assert.Equal(default, value); + } + + [Fact] + public void TryGetUnnamedParameter_ReturnsFalseForJsonObjects() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""parameter"":""value""}")); + + // Act and assert + Assert.False(parameter.TryGetUnnamedParameter(0, out var value)); + Assert.Equal(default, value); + } + + [Fact] + public void TryGetUnnamedParameter_ReturnsExpectedNodeForJsonArray() + { + // Arrange + var parameter = new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam"",""Contoso""]")); + + // Act and assert + Assert.True(parameter.TryGetUnnamedParameter(0, out var value)); + Assert.Equal("Fabrikam", (string?) value); + } + + [Fact] + public void WriteTo_ThrowsAnExceptionForNullWriter() + { + // Arrange + var parameter = new OpenIddictParameter(); + + // Act and assert + var exception = Assert.Throws(() => parameter.WriteTo(writer: null!)); + Assert.Equal("writer", exception.ParamName); + } + + [Fact] + public void WriteTo_WritesUtf8JsonRepresentation() + { + // Arrange + var parameter = new OpenIddictParameter(JsonSerializer.Deserialize(@"{ ""redirect_uris"": [""https://abc.org/callback""], ""client_name"":""My Example Client"" }")); - using var stream = new MemoryStream(); - using var writer = new Utf8JsonWriter(stream); - // Act - parameter.WriteTo(writer); - writer.Flush(); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); - // Assert - Assert.Equal(@"{""redirect_uris"":[""https://abc.org/callback""],""client_name"":""My Example Client""}", - Encoding.UTF8.GetString(stream.ToArray())); - } + // Act + parameter.WriteTo(writer); + writer.Flush(); - [Fact] - public void BoolConverter_CanCreateParameterFromBooleanValue() - { - // Arrange, act and assert - Assert.True((bool?) new OpenIddictParameter(true).Value); - Assert.True((bool?) new OpenIddictParameter((bool?) true).Value); + // Assert + Assert.Equal(@"{""redirect_uris"":[""https://abc.org/callback""],""client_name"":""My Example Client""}", + Encoding.UTF8.GetString(stream.ToArray())); + } - Assert.False((bool?) new OpenIddictParameter(false).Value); - Assert.False((bool?) new OpenIddictParameter((bool?) false).Value); - } + [Fact] + public void BoolConverter_CanCreateParameterFromBooleanValue() + { + // Arrange, act and assert + Assert.True((bool?) new OpenIddictParameter(true).Value); + Assert.True((bool?) new OpenIddictParameter((bool?) true).Value); - [Fact] - public void BoolConverter_ReturnsDefaultValueForNullValues() - { - // Arrange, act and assert - Assert.False((bool) new OpenIddictParameter()); - Assert.False((bool) (OpenIddictParameter?) null); + Assert.False((bool?) new OpenIddictParameter(false).Value); + Assert.False((bool?) new OpenIddictParameter((bool?) false).Value); + } - Assert.Null((bool?) new OpenIddictParameter()); - Assert.Null((bool?) (OpenIddictParameter?) null); - } + [Fact] + public void BoolConverter_ReturnsDefaultValueForNullValues() + { + // Arrange, act and assert + Assert.False((bool) new OpenIddictParameter()); + Assert.False((bool) (OpenIddictParameter?) null); - [Fact] - public void BoolConverter_ReturnsDefaultValueForUnsupportedPrimitiveValues() - { - // Arrange, act and assert - Assert.False((bool) new OpenIddictParameter("Fabrikam")); - Assert.Null((bool?) new OpenIddictParameter("Fabrikam")); - } + Assert.Null((bool?) new OpenIddictParameter()); + Assert.Null((bool?) (OpenIddictParameter?) null); + } - [Fact] - public void BoolConverter_ReturnsDefaultValueForUnsupportedArrays() - { - // Arrange, act and assert - Assert.False((bool) new OpenIddictParameter(new[] { "Fabrikam", "Contoso" })); - Assert.Null((bool?) new OpenIddictParameter(new[] { "Fabrikam", "Contoso" })); - } + [Fact] + public void BoolConverter_ReturnsDefaultValueForUnsupportedPrimitiveValues() + { + // Arrange, act and assert + Assert.False((bool) new OpenIddictParameter("Fabrikam")); + Assert.Null((bool?) new OpenIddictParameter("Fabrikam")); + } - [Fact] - public void BoolConverter_ReturnsDefaultValueForUnsupportedJsonValues() - { - // Arrange, act and assert - Assert.False((bool) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); - Assert.Null((bool?) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); - } + [Fact] + public void BoolConverter_ReturnsDefaultValueForUnsupportedArrays() + { + // Arrange, act and assert + Assert.False((bool) new OpenIddictParameter(new[] { "Fabrikam", "Contoso" })); + Assert.Null((bool?) new OpenIddictParameter(new[] { "Fabrikam", "Contoso" })); + } - [Fact] - public void BoolConverter_CanConvertFromPrimitiveValues() - { - // Arrange, act and assert - Assert.True((bool) new OpenIddictParameter(true)); - Assert.True((bool?) new OpenIddictParameter(true)); - Assert.True((bool) new OpenIddictParameter("true")); - Assert.True((bool?) new OpenIddictParameter("true")); - - Assert.False((bool) new OpenIddictParameter(false)); - Assert.False((bool?) new OpenIddictParameter(false)); - Assert.False((bool) new OpenIddictParameter("false")); - Assert.False((bool?) new OpenIddictParameter("false")); - } - - [Fact] - public void BoolConverter_CanConvertFromJsonValues() - { - // Arrange, act and assert - Assert.True((bool) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":true}").GetProperty("field"))); - Assert.True((bool?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":true}").GetProperty("field"))); - Assert.True((bool) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""true""}").GetProperty("field"))); - Assert.True((bool?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""true""}").GetProperty("field"))); - - Assert.False((bool) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); - Assert.False((bool?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); - Assert.False((bool) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""false""}").GetProperty("field"))); - Assert.False((bool?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""false""}").GetProperty("field"))); - } - - [Fact] - public void JsonElementConverter_ReturnsDefaultValueForNullValues() - { - // Arrange, act and assert - Assert.Equal(JsonValueKind.Undefined, ((JsonElement) new OpenIddictParameter()).ValueKind); - Assert.Equal(JsonValueKind.Undefined, ((JsonElement) (OpenIddictParameter?) null).ValueKind); - } + [Fact] + public void BoolConverter_ReturnsDefaultValueForUnsupportedJsonValues() + { + // Arrange, act and assert + Assert.False((bool) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); + Assert.Null((bool?) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); + } - [Fact] - public void JsonElementConverter_ReturnsDefaultValueForUnsupportedJsonValues() - { - // Arrange, act and assert - Assert.Equal(JsonValueKind.Undefined, ((JsonElement) new OpenIddictParameter(new JsonElement())).ValueKind); - } + [Fact] + public void BoolConverter_CanConvertFromPrimitiveValues() + { + // Arrange, act and assert + Assert.True((bool) new OpenIddictParameter(true)); + Assert.True((bool?) new OpenIddictParameter(true)); + Assert.True((bool) new OpenIddictParameter("true")); + Assert.True((bool?) new OpenIddictParameter("true")); + + Assert.False((bool) new OpenIddictParameter(false)); + Assert.False((bool?) new OpenIddictParameter(false)); + Assert.False((bool) new OpenIddictParameter("false")); + Assert.False((bool?) new OpenIddictParameter("false")); + } - [Fact] - public void JsonElementConverter_CanConvertFromJsonValues() - { - // Arrange and act - var array = (JsonElement) new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]")); - var dictionary = (JsonElement) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""Property"":""value""}")); + [Fact] + public void BoolConverter_CanConvertFromJsonValues() + { + // Arrange, act and assert + Assert.True((bool) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":true}").GetProperty("field"))); + Assert.True((bool?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":true}").GetProperty("field"))); + Assert.True((bool) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""true""}").GetProperty("field"))); + Assert.True((bool?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""true""}").GetProperty("field"))); + + Assert.False((bool) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); + Assert.False((bool?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); + Assert.False((bool) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""false""}").GetProperty("field"))); + Assert.False((bool?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""false""}").GetProperty("field"))); + } - // Assert - Assert.Equal("Contoso", array[0].GetString()); - Assert.Equal("Fabrikam", array[1].GetString()); - Assert.Equal("value", dictionary.GetProperty("Property").GetString()); + [Fact] + public void JsonElementConverter_ReturnsDefaultValueForNullValues() + { + // Arrange, act and assert + Assert.Equal(JsonValueKind.Undefined, ((JsonElement) new OpenIddictParameter()).ValueKind); + Assert.Equal(JsonValueKind.Undefined, ((JsonElement) (OpenIddictParameter?) null).ValueKind); + } - Assert.True(((JsonElement) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":true}").GetProperty("field"))).GetBoolean()); + [Fact] + public void JsonElementConverter_ReturnsDefaultValueForUnsupportedJsonValues() + { + // Arrange, act and assert + Assert.Equal(JsonValueKind.Undefined, ((JsonElement) new OpenIddictParameter(new JsonElement())).ValueKind); + } - Assert.Equal(42, ((JsonElement) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))).GetInt64()); + [Fact] + public void JsonElementConverter_CanConvertFromJsonValues() + { + // Arrange and act + var array = (JsonElement) new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]")); + var dictionary = (JsonElement) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""Property"":""value""}")); - Assert.Equal("Fabrikam", ((JsonElement) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field"))).GetString()); - } + // Assert + Assert.Equal("Contoso", array[0].GetString()); + Assert.Equal("Fabrikam", array[1].GetString()); + Assert.Equal("value", dictionary.GetProperty("Property").GetString()); - [Fact] - public void JsonElementConverter_CanConvertFromSerializedJson() - { - // Arrange and act - var array = (JsonElement) new OpenIddictParameter(@"[""Contoso"",""Fabrikam""]"); - var dictionary = (JsonElement) new OpenIddictParameter(@"{""Property"":""value""}"); - - // Assert - Assert.Equal("Contoso", array[0].GetString()); - Assert.Equal("Fabrikam", array[1].GetString()); - Assert.Equal("value", dictionary.GetProperty("Property").GetString()); - } - - [Fact] - public void JsonElementConverter_CanConvertFromArrays() - { - // Arrange and act - var array = (JsonElement) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" }); + Assert.True(((JsonElement) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":true}").GetProperty("field"))).GetBoolean()); - // Assert - Assert.Equal("Contoso", array[0].GetString()); - Assert.Equal("Fabrikam", array[1].GetString()); - } + Assert.Equal(42, ((JsonElement) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))).GetInt64()); - [Fact] - public void LongConverter_CanCreateParameterFromLongValue() - { - // Arrange, act and assert - Assert.Equal(42, (long?) new OpenIddictParameter(42).Value); - Assert.Equal(42, (long?) new OpenIddictParameter((long?) 42).Value); - } + Assert.Equal("Fabrikam", ((JsonElement) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field"))).GetString()); + } - [Fact] - public void LongConverter_ReturnsDefaultValueForNullValues() - { - // Arrange, act and assert - Assert.Equal(0, (long) new OpenIddictParameter()); - Assert.Null((long?) new OpenIddictParameter()); - } + [Fact] + public void JsonElementConverter_CanConvertFromSerializedJson() + { + // Arrange and act + var array = (JsonElement) new OpenIddictParameter(@"[""Contoso"",""Fabrikam""]"); + var dictionary = (JsonElement) new OpenIddictParameter(@"{""Property"":""value""}"); + + // Assert + Assert.Equal("Contoso", array[0].GetString()); + Assert.Equal("Fabrikam", array[1].GetString()); + Assert.Equal("value", dictionary.GetProperty("Property").GetString()); + } - [Fact] - public void LongConverter_ReturnsDefaultValueForUnsupportedPrimitiveValues() - { - // Arrange, act and assert - Assert.Equal(0, (long) new OpenIddictParameter("Fabrikam")); - Assert.Null((long?) new OpenIddictParameter("Fabrikam")); - } + [Fact] + public void JsonElementConverter_CanConvertFromArrays() + { + // Arrange and act + var array = (JsonElement) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" }); - [Fact] - public void LongConverter_ReturnsDefaultValueForUnsupportedArrays() - { - // Arrange, act and assert - Assert.Equal(0, (long) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" })); - Assert.Null((long?) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" })); - } + // Assert + Assert.Equal("Contoso", array[0].GetString()); + Assert.Equal("Fabrikam", array[1].GetString()); + } - [Fact] - public void LongConverter_ReturnsDefaultValueForUnsupportedJsonValues() - { - // Arrange, act and assert - Assert.Equal(0, (long) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); - Assert.Null((long?) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); - } + [Fact] + public void LongConverter_CanCreateParameterFromLongValue() + { + // Arrange, act and assert + Assert.Equal(42, (long?) new OpenIddictParameter(42).Value); + Assert.Equal(42, (long?) new OpenIddictParameter((long?) 42).Value); + } - [Fact] - public void LongConverter_CanConvertFromPrimitiveValues() - { - // Arrange, act and assert - Assert.Equal(42, (long?) new OpenIddictParameter(42)); - Assert.Equal(42, (long?) new OpenIddictParameter(42)); - Assert.Equal(42, (long?) new OpenIddictParameter(42)); - Assert.Equal(42, (long?) new OpenIddictParameter(42)); - } - - [Fact] - public void LongConverter_CanConvertFromJsonValues() - { - // Arrange, act and assert - Assert.Equal(42, (long?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))); - Assert.Equal(42, (long?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))); - } - - [Fact] - public void StringConverter_CanCreateParameterFromStringValue() - { - // Arrange, act and assert - Assert.Equal("Fabrikam", (string?) new OpenIddictParameter("Fabrikam").Value); - } + [Fact] + public void LongConverter_ReturnsDefaultValueForNullValues() + { + // Arrange, act and assert + Assert.Equal(0, (long) new OpenIddictParameter()); + Assert.Null((long?) new OpenIddictParameter()); + } - [Fact] - public void StringConverter_ReturnsDefaultValueForNullValues() - { - // Arrange, act and assert - Assert.Null((string?) new OpenIddictParameter()); - Assert.Null((string?) (OpenIddictParameter?) null); - } + [Fact] + public void LongConverter_ReturnsDefaultValueForUnsupportedPrimitiveValues() + { + // Arrange, act and assert + Assert.Equal(0, (long) new OpenIddictParameter("Fabrikam")); + Assert.Null((long?) new OpenIddictParameter("Fabrikam")); + } - [Fact] - public void StringConverter_ReturnsDefaultValueForArrays() - { - // Arrange, act and assert - Assert.Null((string?) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" })); - } + [Fact] + public void LongConverter_ReturnsDefaultValueForUnsupportedArrays() + { + // Arrange, act and assert + Assert.Equal(0, (long) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" })); + Assert.Null((long?) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" })); + } - [Fact] - public void StringConverter_ReturnsDefaultValueForUnsupportedJsonValues() - { - // Arrange, act and assert - Assert.Null((string?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]"))); - Assert.Null((string?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}"))); - } - - [Fact] - public void StringConverter_CanConvertFromPrimitiveValues() - { - // Arrange, act and assert - Assert.Equal("Fabrikam", (string?) new OpenIddictParameter("Fabrikam")); - Assert.Equal("False", (string?) new OpenIddictParameter(false)); - Assert.Equal("42", (string?) new OpenIddictParameter(42)); - } - - [Fact] - public void StringConverter_CanConvertFromJsonValues() - { - // Arrange, act and assert - Assert.Equal("Fabrikam", (string?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field"))); - Assert.Equal(bool.FalseString, (string?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); - Assert.Equal("42", (string?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))); - } - - [Fact] - public void StringArrayConverter_CanCreateParameterFromArray() - { - // Arrange - var array = new[] { "Fabrikam", "Contoso" }; + [Fact] + public void LongConverter_ReturnsDefaultValueForUnsupportedJsonValues() + { + // Arrange, act and assert + Assert.Equal(0, (long) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); + Assert.Null((long?) new OpenIddictParameter(JsonSerializer.Deserialize("[]"))); + } - // Act - var parameter = new OpenIddictParameter(array); + [Fact] + public void LongConverter_CanConvertFromPrimitiveValues() + { + // Arrange, act and assert + Assert.Equal(42, (long?) new OpenIddictParameter(42)); + Assert.Equal(42, (long?) new OpenIddictParameter(42)); + Assert.Equal(42, (long?) new OpenIddictParameter(42)); + Assert.Equal(42, (long?) new OpenIddictParameter(42)); + } - // Assert - Assert.Same(array, parameter.Value); - } + [Fact] + public void LongConverter_CanConvertFromJsonValues() + { + // Arrange, act and assert + Assert.Equal(42, (long?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))); + Assert.Equal(42, (long?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))); + } - [Fact] - public void StringArrayConverter_CanCreateParameterFromPrimitiveValues() - { - // Arrange, act and assert - Assert.Equal(new[] { "Fabrikam" }, (string[]?) new OpenIddictParameter("Fabrikam")); - Assert.Equal(new[] { "False" }, (string[]?) new OpenIddictParameter(false)); - Assert.Equal(new[] { "42" }, (string[]?) new OpenIddictParameter(42)); - } - - [Fact] - public void StringArrayConverter_ReturnsDefaultValueForNullValues() - { - // Arrange, act and assert - Assert.Null((string[]?) new OpenIddictParameter()); - } + [Fact] + public void StringConverter_CanCreateParameterFromStringValue() + { + // Arrange, act and assert + Assert.Equal("Fabrikam", (string?) new OpenIddictParameter("Fabrikam").Value); + } - [Fact] - public void StringArrayConverter_ReturnsSingleElementArrayForStringValue() - { - // Arrange, act and assert - Assert.Equal(new[] { "Fabrikam" }, (string[]?) new OpenIddictParameter("Fabrikam")); - } + [Fact] + public void StringConverter_ReturnsDefaultValueForNullValues() + { + // Arrange, act and assert + Assert.Null((string?) new OpenIddictParameter()); + Assert.Null((string?) (OpenIddictParameter?) null); + } - [Fact] - public void StringArrayConverter_ReturnsDefaultValueForUnsupportedJsonValues() - { - // Arrange, act and assert - Assert.Null((string[]?) new OpenIddictParameter(new JsonElement())); - } + [Fact] + public void StringConverter_ReturnsDefaultValueForArrays() + { + // Arrange, act and assert + Assert.Null((string?) new OpenIddictParameter(new[] { "Contoso", "Fabrikam" })); + } - [Fact] - public void StringArrayConverter_CanConvertFromJsonValues() - { - // Arrange, act and assert - Assert.Equal(new[] { "Fabrikam" }, (string[]?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field"))); - Assert.Equal(new[] { "False" }, (string[]?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); - Assert.Equal(new[] { "42" }, (string[]?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))); - Assert.Equal(new[] { "Fabrikam" }, (string[]?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Fabrikam""]"))); - Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) new OpenIddictParameter( - JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]"))); - } + [Fact] + public void StringConverter_ReturnsDefaultValueForUnsupportedJsonValues() + { + // Arrange, act and assert + Assert.Null((string?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]"))); + Assert.Null((string?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}"))); + } + + [Fact] + public void StringConverter_CanConvertFromPrimitiveValues() + { + // Arrange, act and assert + Assert.Equal("Fabrikam", (string?) new OpenIddictParameter("Fabrikam")); + Assert.Equal("False", (string?) new OpenIddictParameter(false)); + Assert.Equal("42", (string?) new OpenIddictParameter(42)); + } + + [Fact] + public void StringConverter_CanConvertFromJsonValues() + { + // Arrange, act and assert + Assert.Equal("Fabrikam", (string?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field"))); + Assert.Equal(bool.FalseString, (string?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); + Assert.Equal("42", (string?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))); + } + + [Fact] + public void StringArrayConverter_CanCreateParameterFromArray() + { + // Arrange + var array = new[] { "Fabrikam", "Contoso" }; + + // Act + var parameter = new OpenIddictParameter(array); + + // Assert + Assert.Same(array, parameter.Value); + } + + [Fact] + public void StringArrayConverter_CanCreateParameterFromPrimitiveValues() + { + // Arrange, act and assert + Assert.Equal(new[] { "Fabrikam" }, (string[]?) new OpenIddictParameter("Fabrikam")); + Assert.Equal(new[] { "False" }, (string[]?) new OpenIddictParameter(false)); + Assert.Equal(new[] { "42" }, (string[]?) new OpenIddictParameter(42)); + } + + [Fact] + public void StringArrayConverter_ReturnsDefaultValueForNullValues() + { + // Arrange, act and assert + Assert.Null((string[]?) new OpenIddictParameter()); + } + + [Fact] + public void StringArrayConverter_ReturnsSingleElementArrayForStringValue() + { + // Arrange, act and assert + Assert.Equal(new[] { "Fabrikam" }, (string[]?) new OpenIddictParameter("Fabrikam")); + } + + [Fact] + public void StringArrayConverter_ReturnsDefaultValueForUnsupportedJsonValues() + { + // Arrange, act and assert + Assert.Null((string[]?) new OpenIddictParameter(new JsonElement())); + } + + [Fact] + public void StringArrayConverter_CanConvertFromJsonValues() + { + // Arrange, act and assert + Assert.Equal(new[] { "Fabrikam" }, (string[]?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":""Fabrikam""}").GetProperty("field"))); + Assert.Equal(new[] { "False" }, (string[]?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":false}").GetProperty("field"))); + Assert.Equal(new[] { "42" }, (string[]?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"{""field"":42}").GetProperty("field"))); + Assert.Equal(new[] { "Fabrikam" }, (string[]?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Fabrikam""]"))); + Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) new OpenIddictParameter( + JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]"))); } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictRequestTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictRequestTests.cs index d397f017..3fe922db 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictRequestTests.cs +++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictRequestTests.cs @@ -10,327 +10,326 @@ using System.Text.Json; using Xunit; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace OpenIddict.Abstractions.Tests.Primitives +namespace OpenIddict.Abstractions.Tests.Primitives; + +public class OpenIddictRequestTests { - public class OpenIddictRequestTests + public static IEnumerable Properties { - public static IEnumerable Properties + get { - get - { - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.AccessToken), - /* name: */ Parameters.AccessToken, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.AcrValues), - /* name: */ Parameters.AcrValues, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Assertion), - /* name: */ Parameters.Assertion, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Audiences), - /* name: */ Parameters.Audience, - /* value: */ new OpenIddictParameter(new[] { "Fabrikam", "Contoso" }) - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Claims), - /* name: */ Parameters.Claims, - /* value: */ new OpenIddictParameter(JsonSerializer.Deserialize(@"{""userinfo"": {}}")) - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.ClaimsLocales), - /* name: */ Parameters.ClaimsLocales, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.ClientAssertion), - /* name: */ Parameters.ClientAssertion, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.ClientAssertionType), - /* name: */ Parameters.ClientAssertionType, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.ClientId), - /* name: */ Parameters.ClientId, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.ClientSecret), - /* name: */ Parameters.ClientSecret, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Code), - /* name: */ Parameters.Code, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.CodeChallenge), - /* name: */ Parameters.CodeChallenge, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.CodeChallengeMethod), - /* name: */ Parameters.CodeChallengeMethod, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.CodeVerifier), - /* name: */ Parameters.CodeVerifier, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.DeviceCode), - /* name: */ Parameters.DeviceCode, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Display), - /* name: */ Parameters.Display, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.GrantType), - /* name: */ Parameters.GrantType, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.IdentityProvider), - /* name: */ Parameters.IdentityProvider, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.IdTokenHint), - /* name: */ Parameters.IdTokenHint, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.LoginHint), - /* name: */ Parameters.LoginHint, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Nonce), - /* name: */ Parameters.Nonce, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.MaxAge), - /* name: */ Parameters.MaxAge, - /* value: */ new OpenIddictParameter((long?) 42) - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Password), - /* name: */ Parameters.Password, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.PostLogoutRedirectUri), - /* name: */ Parameters.PostLogoutRedirectUri, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Prompt), - /* name: */ Parameters.Prompt, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.RedirectUri), - /* name: */ Parameters.RedirectUri, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.RefreshToken), - /* name: */ Parameters.RefreshToken, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Registration), - /* name: */ Parameters.Registration, - /* value: */ new OpenIddictParameter(JsonSerializer.Deserialize(@"{""policy_uri"": ""http://www.fabrikam.com/policy""}")) - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Request), - /* name: */ Parameters.Request, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.RequestId), - /* name: */ Parameters.RequestId, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.RequestUri), - /* name: */ Parameters.RequestUri, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Resources), - /* name: */ Parameters.Resource, - /* value: */ new OpenIddictParameter(new[] { "https://fabrikam.com/", "https://contoso.com/" }) - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.ResponseMode), - /* name: */ Parameters.ResponseMode, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.ResponseType), - /* name: */ Parameters.ResponseType, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Scope), - /* name: */ Parameters.Scope, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.State), - /* name: */ Parameters.State, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Token), - /* name: */ Parameters.Token, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.TokenTypeHint), - /* name: */ Parameters.TokenTypeHint, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.UiLocales), - /* name: */ Parameters.UiLocales, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.UserCode), - /* name: */ Parameters.UserCode, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictRequest.Username), - /* name: */ Parameters.Username, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - } - } + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.AccessToken), + /* name: */ Parameters.AccessToken, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; - [Theory] - [MemberData(nameof(Properties))] - public void PropertyGetter_ReturnsExpectedParameter(string property, string name, OpenIddictParameter value) - { - // Arrange - var request = new OpenIddictRequest(); - request.SetParameter(name, value); + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.AcrValues), + /* name: */ Parameters.AcrValues, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; - // Act and assert - Assert.Equal(value.Value, typeof(OpenIddictRequest).GetProperty(property)!.GetValue(request)); - } + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Assertion), + /* name: */ Parameters.Assertion, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; - [Theory] - [MemberData(nameof(Properties))] - public void PropertySetter_AddsExpectedParameter(string property, string name, OpenIddictParameter value) - { - // Arrange - var request = new OpenIddictRequest(); + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Audiences), + /* name: */ Parameters.Audience, + /* value: */ new OpenIddictParameter(new[] { "Fabrikam", "Contoso" }) + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Claims), + /* name: */ Parameters.Claims, + /* value: */ new OpenIddictParameter(JsonSerializer.Deserialize(@"{""userinfo"": {}}")) + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.ClaimsLocales), + /* name: */ Parameters.ClaimsLocales, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.ClientAssertion), + /* name: */ Parameters.ClientAssertion, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.ClientAssertionType), + /* name: */ Parameters.ClientAssertionType, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.ClientId), + /* name: */ Parameters.ClientId, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.ClientSecret), + /* name: */ Parameters.ClientSecret, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Code), + /* name: */ Parameters.Code, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.CodeChallenge), + /* name: */ Parameters.CodeChallenge, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.CodeChallengeMethod), + /* name: */ Parameters.CodeChallengeMethod, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.CodeVerifier), + /* name: */ Parameters.CodeVerifier, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.DeviceCode), + /* name: */ Parameters.DeviceCode, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Display), + /* name: */ Parameters.Display, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.GrantType), + /* name: */ Parameters.GrantType, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.IdentityProvider), + /* name: */ Parameters.IdentityProvider, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.IdTokenHint), + /* name: */ Parameters.IdTokenHint, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.LoginHint), + /* name: */ Parameters.LoginHint, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Nonce), + /* name: */ Parameters.Nonce, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.MaxAge), + /* name: */ Parameters.MaxAge, + /* value: */ new OpenIddictParameter((long?) 42) + }; - // Act - typeof(OpenIddictRequest).GetProperty(property)!.SetValue(request, value.Value); + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Password), + /* name: */ Parameters.Password, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.PostLogoutRedirectUri), + /* name: */ Parameters.PostLogoutRedirectUri, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Prompt), + /* name: */ Parameters.Prompt, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.RedirectUri), + /* name: */ Parameters.RedirectUri, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.RefreshToken), + /* name: */ Parameters.RefreshToken, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Registration), + /* name: */ Parameters.Registration, + /* value: */ new OpenIddictParameter(JsonSerializer.Deserialize(@"{""policy_uri"": ""http://www.fabrikam.com/policy""}")) + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Request), + /* name: */ Parameters.Request, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.RequestId), + /* name: */ Parameters.RequestId, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.RequestUri), + /* name: */ Parameters.RequestUri, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Resources), + /* name: */ Parameters.Resource, + /* value: */ new OpenIddictParameter(new[] { "https://fabrikam.com/", "https://contoso.com/" }) + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.ResponseMode), + /* name: */ Parameters.ResponseMode, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.ResponseType), + /* name: */ Parameters.ResponseType, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; - // Assert - Assert.Equal(value, request.GetParameter(name)); + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Scope), + /* name: */ Parameters.Scope, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.State), + /* name: */ Parameters.State, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Token), + /* name: */ Parameters.Token, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.TokenTypeHint), + /* name: */ Parameters.TokenTypeHint, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.UiLocales), + /* name: */ Parameters.UiLocales, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.UserCode), + /* name: */ Parameters.UserCode, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictRequest.Username), + /* name: */ Parameters.Username, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; } } + + [Theory] + [MemberData(nameof(Properties))] + public void PropertyGetter_ReturnsExpectedParameter(string property, string name, OpenIddictParameter value) + { + // Arrange + var request = new OpenIddictRequest(); + request.SetParameter(name, value); + + // Act and assert + Assert.Equal(value.Value, typeof(OpenIddictRequest).GetProperty(property)!.GetValue(request)); + } + + [Theory] + [MemberData(nameof(Properties))] + public void PropertySetter_AddsExpectedParameter(string property, string name, OpenIddictParameter value) + { + // Arrange + var request = new OpenIddictRequest(); + + // Act + typeof(OpenIddictRequest).GetProperty(property)!.SetValue(request, value.Value); + + // Assert + Assert.Equal(value, request.GetParameter(name)); + } } diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictResponseTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictResponseTests.cs index 63da9f02..6b6ad52c 100644 --- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictResponseTests.cs +++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictResponseTests.cs @@ -9,131 +9,130 @@ using System.Collections.Generic; using Xunit; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace OpenIddict.Abstractions.Tests.Primitives +namespace OpenIddict.Abstractions.Tests.Primitives; + +public class OpenIddictResponseTests { - public class OpenIddictResponseTests + public static IEnumerable Properties { - public static IEnumerable Properties + get { - get + yield return new object[] { - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.AccessToken), - /* name: */ Parameters.AccessToken, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.Code), - /* name: */ Parameters.Code, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.DeviceCode), - /* name: */ Parameters.DeviceCode, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.Error), - /* name: */ Parameters.Error, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.ErrorDescription), - /* name: */ Parameters.ErrorDescription, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.ErrorUri), - /* name: */ Parameters.ErrorUri, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.ExpiresIn), - /* name: */ Parameters.ExpiresIn, - /* value: */ new OpenIddictParameter((long?) 42) - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.IdToken), - /* name: */ Parameters.IdToken, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.RefreshToken), - /* name: */ Parameters.RefreshToken, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.Scope), - /* name: */ Parameters.Scope, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.State), - /* name: */ Parameters.State, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.TokenType), - /* name: */ Parameters.TokenType, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - - yield return new object[] - { - /* property: */ nameof(OpenIddictResponse.UserCode), - /* name: */ Parameters.UserCode, - /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") - }; - } - } + /* property: */ nameof(OpenIddictResponse.AccessToken), + /* name: */ Parameters.AccessToken, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; - [Theory] - [MemberData(nameof(Properties))] - public void PropertyGetter_ReturnsExpectedParameter(string property, string name, OpenIddictParameter value) - { - // Arrange - var response = new OpenIddictResponse(); - response.SetParameter(name, value); + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.Code), + /* name: */ Parameters.Code, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; - // Act and assert - Assert.Equal(value.Value, typeof(OpenIddictResponse).GetProperty(property)!.GetValue(response)); - } + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.DeviceCode), + /* name: */ Parameters.DeviceCode, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; - [Theory] - [MemberData(nameof(Properties))] - public void PropertySetter_AddsExpectedParameter(string property, string name, OpenIddictParameter value) - { - // Arrange - var response = new OpenIddictResponse(); + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.Error), + /* name: */ Parameters.Error, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; - // Act - typeof(OpenIddictResponse).GetProperty(property)!.SetValue(response, value.Value); + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.ErrorDescription), + /* name: */ Parameters.ErrorDescription, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.ErrorUri), + /* name: */ Parameters.ErrorUri, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.ExpiresIn), + /* name: */ Parameters.ExpiresIn, + /* value: */ new OpenIddictParameter((long?) 42) + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.IdToken), + /* name: */ Parameters.IdToken, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.RefreshToken), + /* name: */ Parameters.RefreshToken, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; - // Assert - Assert.Equal(value, response.GetParameter(name)); + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.Scope), + /* name: */ Parameters.Scope, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.State), + /* name: */ Parameters.State, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.TokenType), + /* name: */ Parameters.TokenType, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; + + yield return new object[] + { + /* property: */ nameof(OpenIddictResponse.UserCode), + /* name: */ Parameters.UserCode, + /* value: */ new OpenIddictParameter("802A3E3E-DCCA-4EFC-89FA-7D82FE8C27E4") + }; } } + + [Theory] + [MemberData(nameof(Properties))] + public void PropertyGetter_ReturnsExpectedParameter(string property, string name, OpenIddictParameter value) + { + // Arrange + var response = new OpenIddictResponse(); + response.SetParameter(name, value); + + // Act and assert + Assert.Equal(value.Value, typeof(OpenIddictResponse).GetProperty(property)!.GetValue(response)); + } + + [Theory] + [MemberData(nameof(Properties))] + public void PropertySetter_AddsExpectedParameter(string property, string name, OpenIddictParameter value) + { + // Arrange + var response = new OpenIddictResponse(); + + // Act + typeof(OpenIddictResponse).GetProperty(property)!.SetValue(response, value.Value); + + // Assert + Assert.Equal(value, response.GetParameter(name)); + } } diff --git a/test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs b/test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs index 0055e162..6896fbee 100644 --- a/test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs +++ b/test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs @@ -13,767 +13,766 @@ using OpenIddict.Abstractions; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core.Tests +namespace OpenIddict.Core.Tests; + +public class OpenIddictCoreBuilderTests { - public class OpenIddictCoreBuilderTests + [Fact] + public void Constructor_ThrowsAnExceptionForNullServices() { - [Fact] - public void Constructor_ThrowsAnExceptionForNullServices() - { - // Arrange - var services = (IServiceCollection) null!; + // Arrange + var services = (IServiceCollection) null!; - // Act and assert - var exception = Assert.Throws(() => new OpenIddictCoreBuilder(services)); + // Act and assert + var exception = Assert.Throws(() => new OpenIddictCoreBuilder(services)); - Assert.Equal("services", exception.ParamName); - } + Assert.Equal("services", exception.ParamName); + } - [Fact] - public void ReplaceApplicationManager_ThrowsAnExceptionForInvalidManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceApplicationManager_ThrowsAnExceptionForInvalidManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.ReplaceApplicationManager(typeof(object))); + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceApplicationManager(typeof(object))); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - [Fact] - public void ReplaceApplicationManager_OverridesDefaultOpenGenericManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.ReplaceApplicationManager(typeof(OpenGenericApplicationManager<>)); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(OpenGenericApplicationManager<>) && - service.ImplementationType == typeof(OpenGenericApplicationManager<>)); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictApplicationManager<>) && - service.ImplementationType == typeof(OpenGenericApplicationManager<>)); - Assert.DoesNotContain(services, service => - service.ServiceType == typeof(OpenIddictApplicationManager<>) && - service.ImplementationType == typeof(OpenIddictApplicationManager<>)); - } + [Fact] + public void ReplaceApplicationManager_OverridesDefaultOpenGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceApplicationManager(typeof(OpenGenericApplicationManager<>)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenGenericApplicationManager<>) && + service.ImplementationType == typeof(OpenGenericApplicationManager<>)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictApplicationManager<>) && + service.ImplementationType == typeof(OpenGenericApplicationManager<>)); + Assert.DoesNotContain(services, service => + service.ServiceType == typeof(OpenIddictApplicationManager<>) && + service.ImplementationType == typeof(OpenIddictApplicationManager<>)); + } - [Fact] - public void ReplaceApplicationManager_AddsClosedGenericManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.ReplaceApplicationManager(typeof(ClosedGenericApplicationManager)); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(ClosedGenericApplicationManager) && - service.ImplementationFactory is not null); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictApplicationManager) && - service.ImplementationType == typeof(ClosedGenericApplicationManager)); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictApplicationManager<>) && - service.ImplementationType == typeof(OpenIddictApplicationManager<>)); - } + [Fact] + public void ReplaceApplicationManager_AddsClosedGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceApplicationManager(typeof(ClosedGenericApplicationManager)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(ClosedGenericApplicationManager) && + service.ImplementationFactory is not null); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictApplicationManager) && + service.ImplementationType == typeof(ClosedGenericApplicationManager)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictApplicationManager<>) && + service.ImplementationType == typeof(OpenIddictApplicationManager<>)); + } - [Fact] - public void ReplaceApplicationStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceApplicationStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.ReplaceApplicationStoreResolver(typeof(object))); + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceApplicationStoreResolver(typeof(object))); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - [Fact] - public void ReplaceApplicationStoreResolver_OverridesDefaultResolver() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceApplicationStoreResolver_OverridesDefaultResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - var type = Mock.Of().GetType(); + var type = Mock.Of().GetType(); - // Act - builder.ReplaceApplicationStoreResolver(type); + // Act + builder.ReplaceApplicationStoreResolver(type); - var provider = services.BuildServiceProvider(); - var store = provider.GetRequiredService(); + var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService(); - // Assert - Assert.IsType(type, store); - } + // Assert + Assert.IsType(type, store); + } - [Fact] - public void ReplaceAuthorizationManager_ThrowsAnExceptionForInvalidManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceAuthorizationManager_ThrowsAnExceptionForInvalidManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.ReplaceAuthorizationManager(typeof(object))); + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceAuthorizationManager(typeof(object))); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - [Fact] - public void ReplaceAuthorizationManager_OverridesDefaultOpenGenericManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.ReplaceAuthorizationManager(typeof(OpenGenericAuthorizationManager<>)); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(OpenGenericAuthorizationManager<>) && - service.ImplementationType == typeof(OpenGenericAuthorizationManager<>)); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictAuthorizationManager<>) && - service.ImplementationType == typeof(OpenGenericAuthorizationManager<>)); - Assert.DoesNotContain(services, service => - service.ServiceType == typeof(OpenIddictAuthorizationManager<>) && - service.ImplementationType == typeof(OpenIddictAuthorizationManager<>)); - } + [Fact] + public void ReplaceAuthorizationManager_OverridesDefaultOpenGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceAuthorizationManager(typeof(OpenGenericAuthorizationManager<>)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenGenericAuthorizationManager<>) && + service.ImplementationType == typeof(OpenGenericAuthorizationManager<>)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictAuthorizationManager<>) && + service.ImplementationType == typeof(OpenGenericAuthorizationManager<>)); + Assert.DoesNotContain(services, service => + service.ServiceType == typeof(OpenIddictAuthorizationManager<>) && + service.ImplementationType == typeof(OpenIddictAuthorizationManager<>)); + } - [Fact] - public void ReplaceAuthorizationManager_AddsClosedGenericManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.ReplaceAuthorizationManager(typeof(ClosedGenericAuthorizationManager)); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(ClosedGenericAuthorizationManager) && - service.ImplementationFactory is not null); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictAuthorizationManager) && - service.ImplementationType == typeof(ClosedGenericAuthorizationManager)); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictAuthorizationManager<>) && - service.ImplementationType == typeof(OpenIddictAuthorizationManager<>)); - } - [Fact] - public void ReplaceAuthorizationStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceAuthorizationManager_AddsClosedGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceAuthorizationManager(typeof(ClosedGenericAuthorizationManager)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(ClosedGenericAuthorizationManager) && + service.ImplementationFactory is not null); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictAuthorizationManager) && + service.ImplementationType == typeof(ClosedGenericAuthorizationManager)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictAuthorizationManager<>) && + service.ImplementationType == typeof(OpenIddictAuthorizationManager<>)); + } + [Fact] + public void ReplaceAuthorizationStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.ReplaceAuthorizationStoreResolver(typeof(object))); + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceAuthorizationStoreResolver(typeof(object))); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - [Fact] - public void ReplaceAuthorizationStoreResolver_OverridesDefaultResolver() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceAuthorizationStoreResolver_OverridesDefaultResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - var type = Mock.Of().GetType(); + var type = Mock.Of().GetType(); - // Act - builder.ReplaceAuthorizationStoreResolver(type); + // Act + builder.ReplaceAuthorizationStoreResolver(type); - var provider = services.BuildServiceProvider(); - var store = provider.GetRequiredService(); + var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService(); - // Assert - Assert.IsType(type, store); - } + // Assert + Assert.IsType(type, store); + } - [Fact] - public void ReplaceScopeManager_ThrowsAnExceptionForInvalidManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceScopeManager_ThrowsAnExceptionForInvalidManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.ReplaceScopeManager(typeof(object))); + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceScopeManager(typeof(object))); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - [Fact] - public void ReplaceScopeManager_OverridesDefaultOpenGenericManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.ReplaceScopeManager(typeof(OpenGenericScopeManager<>)); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(OpenGenericScopeManager<>) && - service.ImplementationType == typeof(OpenGenericScopeManager<>)); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictScopeManager<>) && - service.ImplementationType == typeof(OpenGenericScopeManager<>)); - Assert.DoesNotContain(services, service => - service.ServiceType == typeof(OpenIddictScopeManager<>) && - service.ImplementationType == typeof(OpenIddictScopeManager<>)); - } + [Fact] + public void ReplaceScopeManager_OverridesDefaultOpenGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceScopeManager(typeof(OpenGenericScopeManager<>)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenGenericScopeManager<>) && + service.ImplementationType == typeof(OpenGenericScopeManager<>)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictScopeManager<>) && + service.ImplementationType == typeof(OpenGenericScopeManager<>)); + Assert.DoesNotContain(services, service => + service.ServiceType == typeof(OpenIddictScopeManager<>) && + service.ImplementationType == typeof(OpenIddictScopeManager<>)); + } - [Fact] - public void ReplaceScopeManager_AddsClosedGenericManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.ReplaceScopeManager(typeof(ClosedGenericScopeManager)); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(ClosedGenericScopeManager) && - service.ImplementationFactory is not null); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictScopeManager) && - service.ImplementationType == typeof(ClosedGenericScopeManager)); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictScopeManager<>) && - service.ImplementationType == typeof(OpenIddictScopeManager<>)); - } + [Fact] + public void ReplaceScopeManager_AddsClosedGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceScopeManager(typeof(ClosedGenericScopeManager)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(ClosedGenericScopeManager) && + service.ImplementationFactory is not null); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictScopeManager) && + service.ImplementationType == typeof(ClosedGenericScopeManager)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictScopeManager<>) && + service.ImplementationType == typeof(OpenIddictScopeManager<>)); + } - [Fact] - public void ReplaceScopeStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceScopeStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.ReplaceScopeStoreResolver(typeof(object))); + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceScopeStoreResolver(typeof(object))); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - [Fact] - public void ReplaceScopeStoreResolver_OverridesDefaultResolver() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceScopeStoreResolver_OverridesDefaultResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - var type = Mock.Of().GetType(); + var type = Mock.Of().GetType(); - // Act - builder.ReplaceScopeStoreResolver(type); + // Act + builder.ReplaceScopeStoreResolver(type); - var provider = services.BuildServiceProvider(); - var store = provider.GetRequiredService(); + var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService(); - // Assert - Assert.IsType(type, store); - } + // Assert + Assert.IsType(type, store); + } - [Fact] - public void ReplaceTokenManager_ThrowsAnExceptionForInvalidManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceTokenManager_ThrowsAnExceptionForInvalidManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.ReplaceTokenManager(typeof(object))); + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceTokenManager(typeof(object))); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - [Fact] - public void ReplaceTokenManager_OverridesDefaultOpenGenericManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.ReplaceTokenManager(typeof(OpenGenericTokenManager<>)); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(OpenGenericTokenManager<>) && - service.ImplementationType == typeof(OpenGenericTokenManager<>)); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictTokenManager<>) && - service.ImplementationType == typeof(OpenGenericTokenManager<>)); - Assert.DoesNotContain(services, service => - service.ServiceType == typeof(OpenIddictTokenManager<>) && - service.ImplementationType == typeof(OpenIddictTokenManager<>)); - } + [Fact] + public void ReplaceTokenManager_OverridesDefaultOpenGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceTokenManager(typeof(OpenGenericTokenManager<>)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(OpenGenericTokenManager<>) && + service.ImplementationType == typeof(OpenGenericTokenManager<>)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictTokenManager<>) && + service.ImplementationType == typeof(OpenGenericTokenManager<>)); + Assert.DoesNotContain(services, service => + service.ServiceType == typeof(OpenIddictTokenManager<>) && + service.ImplementationType == typeof(OpenIddictTokenManager<>)); + } - [Fact] - public void ReplaceTokenManager_AddsClosedGenericManager() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.ReplaceTokenManager(typeof(ClosedGenericTokenManager)); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(ClosedGenericTokenManager) && - service.ImplementationFactory is not null); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictTokenManager) && - service.ImplementationType == typeof(ClosedGenericTokenManager)); - Assert.Contains(services, service => - service.ServiceType == typeof(OpenIddictTokenManager<>) && - service.ImplementationType == typeof(OpenIddictTokenManager<>)); - } + [Fact] + public void ReplaceTokenManager_AddsClosedGenericManager() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.ReplaceTokenManager(typeof(ClosedGenericTokenManager)); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(ClosedGenericTokenManager) && + service.ImplementationFactory is not null); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictTokenManager) && + service.ImplementationType == typeof(ClosedGenericTokenManager)); + Assert.Contains(services, service => + service.ServiceType == typeof(OpenIddictTokenManager<>) && + service.ImplementationType == typeof(OpenIddictTokenManager<>)); + } - [Fact] - public void ReplaceTokenStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceTokenStoreResolver_ThrowsAnExceptionForInvalidStoreResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.ReplaceTokenStoreResolver(typeof(object))); + // Act and assert + var exception = Assert.Throws(() => builder.ReplaceTokenStoreResolver(typeof(object))); - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - [Fact] - public void ReplaceTokenStoreResolver_OverridesDefaultResolver() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceTokenStoreResolver_OverridesDefaultResolver() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - var type = Mock.Of().GetType(); + var type = Mock.Of().GetType(); - // Act - builder.ReplaceTokenStoreResolver(type); + // Act + builder.ReplaceTokenStoreResolver(type); - var provider = services.BuildServiceProvider(); - var store = provider.GetRequiredService(); + var provider = services.BuildServiceProvider(); + var store = provider.GetRequiredService(); - // Assert - Assert.IsType(type, store); - } + // Assert + Assert.IsType(type, store); + } - [Fact] - public void DisableAdditionalFiltering_FilteringIsCorrectlyDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void DisableAdditionalFiltering_FilteringIsCorrectlyDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.DisableAdditionalFiltering(); + // Act + builder.DisableAdditionalFiltering(); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - Assert.True(options.DisableAdditionalFiltering); - } + Assert.True(options.DisableAdditionalFiltering); + } - [Fact] - public void DisableEntityCaching_CachingIsCorrectlyDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void DisableEntityCaching_CachingIsCorrectlyDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.DisableEntityCaching(); + // Act + builder.DisableEntityCaching(); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - Assert.True(options.DisableEntityCaching); - } + Assert.True(options.DisableEntityCaching); + } - [Fact] - public void SetDefaultApplicationEntity_ThrowsAnExceptionForNullType() + [Fact] + public void SetDefaultApplicationEntity_ThrowsAnExceptionForNullType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + return builder.SetDefaultApplicationEntity(type: null!); + }); - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.SetDefaultApplicationEntity(type: null!); - }); + Assert.Equal("type", exception.ParamName); + } - Assert.Equal("type", exception.ParamName); - } + [Fact] + public void SetDefaultApplicationEntity_ThrowsAnExceptionForInvalidType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void SetDefaultApplicationEntity_ThrowsAnExceptionForInvalidType() + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.SetDefaultApplicationEntity(typeof(long)); - }); - - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + return builder.SetDefaultApplicationEntity(typeof(long)); + }); - [Fact] - public void SetDefaultApplicationEntity_EntityIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - // Act - builder.SetDefaultApplicationEntity(); + [Fact] + public void SetDefaultApplicationEntity_EntityIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.SetDefaultApplicationEntity(); - Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; + + Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType); + } + + [Fact] + public void SetDefaultAuthorizationEntity_ThrowsAnExceptionForNullType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void SetDefaultAuthorizationEntity_ThrowsAnExceptionForNullType() + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + return builder.SetDefaultAuthorizationEntity(type: null!); + }); - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.SetDefaultAuthorizationEntity(type: null!); - }); + Assert.Equal("type", exception.ParamName); + } - Assert.Equal("type", exception.ParamName); - } + [Fact] + public void SetDefaultAuthorizationEntity_ThrowsAnExceptionForInvalidType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void SetDefaultAuthorizationEntity_ThrowsAnExceptionForInvalidType() + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.SetDefaultAuthorizationEntity(typeof(long)); - }); - - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + return builder.SetDefaultAuthorizationEntity(typeof(long)); + }); - [Fact] - public void SetDefaultAuthorizationEntity_EntityIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - // Act - builder.SetDefaultAuthorizationEntity(); + [Fact] + public void SetDefaultAuthorizationEntity_EntityIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.SetDefaultAuthorizationEntity(); - Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; + + Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType); + } - [Fact] - public void SetDefaultScopeEntity_ThrowsAnExceptionForNullType() + [Fact] + public void SetDefaultScopeEntity_ThrowsAnExceptionForNullType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + return builder.SetDefaultScopeEntity(type: null!); + }); - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.SetDefaultScopeEntity(type: null!); - }); + Assert.Equal("type", exception.ParamName); + } - Assert.Equal("type", exception.ParamName); - } + [Fact] + public void SetDefaultScopeEntity_ThrowsAnExceptionForInvalidType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void SetDefaultScopeEntity_ThrowsAnExceptionForInvalidType() + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.SetDefaultScopeEntity(typeof(long)); - }); - - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + return builder.SetDefaultScopeEntity(typeof(long)); + }); - [Fact] - public void SetDefaultScopeEntity_EntityIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - // Act - builder.SetDefaultScopeEntity(); + [Fact] + public void SetDefaultScopeEntity_EntityIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.SetDefaultScopeEntity(); - Assert.Equal(typeof(CustomScope), options.DefaultScopeType); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - [Fact] - public void SetDefaultTokenEntity_ThrowsAnExceptionForNullType() + Assert.Equal(typeof(CustomScope), options.DefaultScopeType); + } + + [Fact] + public void SetDefaultTokenEntity_ThrowsAnExceptionForNullType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + return builder.SetDefaultTokenEntity(type: null!); + }); - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.SetDefaultTokenEntity(type: null!); - }); + Assert.Equal("type", exception.ParamName); + } - Assert.Equal("type", exception.ParamName); - } + [Fact] + public void SetDefaultTokenEntity_ThrowsAnExceptionForInvalidType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void SetDefaultTokenEntity_ThrowsAnExceptionForInvalidType() + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.SetDefaultTokenEntity(typeof(long)); - }); - - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + return builder.SetDefaultTokenEntity(typeof(long)); + }); - [Fact] - public void SetDefaultTokenEntity_EntityIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - // Act - builder.SetDefaultTokenEntity(); + [Fact] + public void SetDefaultTokenEntity_EntityIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.SetDefaultTokenEntity(); - Assert.Equal(typeof(CustomToken), options.DefaultTokenType); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - [Theory] - [InlineData(-10)] - [InlineData(0)] - [InlineData(9)] - public void SetEntityCacheLimit_ThrowsAnExceptionForInvalidLimit(int limit) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal(typeof(CustomToken), options.DefaultTokenType); + } - // Act and assert - var exception = Assert.Throws(() => builder.SetEntityCacheLimit(limit)); + [Theory] + [InlineData(-10)] + [InlineData(0)] + [InlineData(9)] + public void SetEntityCacheLimit_ThrowsAnExceptionForInvalidLimit(int limit) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - Assert.Equal("limit", exception.ParamName); - Assert.StartsWith("The cache size cannot be less than 10.", exception.Message); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetEntityCacheLimit(limit)); - [Fact] - public void SetEntityCacheLimit_LimitIsCorrectlyDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal("limit", exception.ParamName); + Assert.StartsWith("The cache size cannot be less than 10.", exception.Message); + } - // Act - builder.SetEntityCacheLimit(42); + [Fact] + public void SetEntityCacheLimit_LimitIsCorrectlyDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.SetEntityCacheLimit(42); - Assert.Equal(42, options.EntityCacheLimit); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - private static OpenIddictCoreBuilder CreateBuilder(IServiceCollection services) - => services.AddOpenIddict().AddCore(); + Assert.Equal(42, options.EntityCacheLimit); + } - private static IServiceCollection CreateServices() - { - var services = new ServiceCollection(); - services.AddOptions(); + private static OpenIddictCoreBuilder CreateBuilder(IServiceCollection services) + => services.AddOpenIddict().AddCore(); - return services; - } + private static IServiceCollection CreateServices() + { + var services = new ServiceCollection(); + services.AddOptions(); + + return services; + } - public class CustomApplication { } - public class CustomAuthorization { } - public class CustomScope { } - public class CustomToken { } + public class CustomApplication { } + public class CustomAuthorization { } + public class CustomScope { } + public class CustomToken { } - private class ClosedGenericApplicationManager : OpenIddictApplicationManager + private class ClosedGenericApplicationManager : OpenIddictApplicationManager + { + public ClosedGenericApplicationManager( + IOpenIddictApplicationCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictApplicationStoreResolver resolver) + : base(cache, logger, options, resolver) { - public ClosedGenericApplicationManager( - IOpenIddictApplicationCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictApplicationStoreResolver resolver) - : base(cache, logger, options, resolver) - { - } } + } - private class OpenGenericApplicationManager : OpenIddictApplicationManager - where TApplication : class + private class OpenGenericApplicationManager : OpenIddictApplicationManager + where TApplication : class + { + public OpenGenericApplicationManager( + IOpenIddictApplicationCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictApplicationStoreResolver resolver) + : base(cache, logger, options, resolver) { - public OpenGenericApplicationManager( - IOpenIddictApplicationCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictApplicationStoreResolver resolver) - : base(cache, logger, options, resolver) - { - } } + } - private class ClosedGenericAuthorizationManager : OpenIddictAuthorizationManager + private class ClosedGenericAuthorizationManager : OpenIddictAuthorizationManager + { + public ClosedGenericAuthorizationManager( + IOpenIddictAuthorizationCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictAuthorizationStoreResolver resolver) + : base(cache, logger, options, resolver) { - public ClosedGenericAuthorizationManager( - IOpenIddictAuthorizationCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictAuthorizationStoreResolver resolver) - : base(cache, logger, options, resolver) - { - } } + } - private class OpenGenericAuthorizationManager : OpenIddictAuthorizationManager - where TAuthorization : class + private class OpenGenericAuthorizationManager : OpenIddictAuthorizationManager + where TAuthorization : class + { + public OpenGenericAuthorizationManager( + IOpenIddictAuthorizationCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictAuthorizationStoreResolver resolver) + : base(cache, logger, options, resolver) { - public OpenGenericAuthorizationManager( - IOpenIddictAuthorizationCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictAuthorizationStoreResolver resolver) - : base(cache, logger, options, resolver) - { - } } + } - private class ClosedGenericScopeManager : OpenIddictScopeManager + private class ClosedGenericScopeManager : OpenIddictScopeManager + { + public ClosedGenericScopeManager( + IOpenIddictScopeCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictScopeStoreResolver resolver) + : base(cache, logger, options, resolver) { - public ClosedGenericScopeManager( - IOpenIddictScopeCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictScopeStoreResolver resolver) - : base(cache, logger, options, resolver) - { - } } + } - private class OpenGenericScopeManager : OpenIddictScopeManager - where TScope : class + private class OpenGenericScopeManager : OpenIddictScopeManager + where TScope : class + { + public OpenGenericScopeManager( + IOpenIddictScopeCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictScopeStoreResolver resolver) + : base(cache, logger, options, resolver) { - public OpenGenericScopeManager( - IOpenIddictScopeCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictScopeStoreResolver resolver) - : base(cache, logger, options, resolver) - { - } } + } - private class ClosedGenericTokenManager : OpenIddictTokenManager + private class ClosedGenericTokenManager : OpenIddictTokenManager + { + public ClosedGenericTokenManager( + IOpenIddictTokenCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictTokenStoreResolver resolver) + : base(cache, logger, options, resolver) { - public ClosedGenericTokenManager( - IOpenIddictTokenCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictTokenStoreResolver resolver) - : base(cache, logger, options, resolver) - { - } } + } - private class OpenGenericTokenManager : OpenIddictTokenManager - where TToken : class + private class OpenGenericTokenManager : OpenIddictTokenManager + where TToken : class + { + public OpenGenericTokenManager( + IOpenIddictTokenCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictTokenStoreResolver resolver) + : base(cache, logger, options, resolver) { - public OpenGenericTokenManager( - IOpenIddictTokenCache cache, - ILogger> logger, - IOptionsMonitor options, - IOpenIddictTokenStoreResolver resolver) - : base(cache, logger, options, resolver) - { - } } } } diff --git a/test/OpenIddict.Core.Tests/OpenIddictCoreExtensionsTests.cs b/test/OpenIddict.Core.Tests/OpenIddictCoreExtensionsTests.cs index 74f01012..a05a0148 100644 --- a/test/OpenIddict.Core.Tests/OpenIddictCoreExtensionsTests.cs +++ b/test/OpenIddict.Core.Tests/OpenIddictCoreExtensionsTests.cs @@ -13,289 +13,288 @@ using OpenIddict.Abstractions; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core.Tests +namespace OpenIddict.Core.Tests; + +public class OpenIddictCoreExtensionsTests { - public class OpenIddictCoreExtensionsTests + [Fact] + public void AddCore_ThrowsAnExceptionForNullBuilder() { - [Fact] - public void AddCore_ThrowsAnExceptionForNullBuilder() - { - // Arrange - var builder = (OpenIddictBuilder) null!; + // Arrange + var builder = (OpenIddictBuilder) null!; - // Act and assert - var exception = Assert.Throws(() => builder.AddCore()); + // Act and assert + var exception = Assert.Throws(() => builder.AddCore()); - Assert.Equal("builder", exception.ParamName); - } + Assert.Equal("builder", exception.ParamName); + } - [Fact] - public void AddCore_ThrowsAnExceptionForNullConfiguration() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); + [Fact] + public void AddCore_ThrowsAnExceptionForNullConfiguration() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.AddCore(configuration: null!)); + // Act and assert + var exception = Assert.Throws(() => builder.AddCore(configuration: null!)); - Assert.Equal("configuration", exception.ParamName); - } + Assert.Equal("configuration", exception.ParamName); + } - [Fact] - public void AddCore_RegistersLoggingServices() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); + [Fact] + public void AddCore_RegistersLoggingServices() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); - // Act - builder.AddCore(); + // Act + builder.AddCore(); - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(ILogger<>)); - } + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(ILogger<>)); + } - [Fact] - public void AddCore_RegistersOptionsServices() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddCore(); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(IOptions<>)); - } - - [Theory] - [InlineData(typeof(OpenIddictApplicationManager<>))] - [InlineData(typeof(OpenIddictAuthorizationManager<>))] - [InlineData(typeof(OpenIddictScopeManager<>))] - [InlineData(typeof(OpenIddictTokenManager<>))] - public void AddCore_RegistersDefaultManagers(Type type) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddCore(); - - // Assert - Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type); - } - - [Theory] - [InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictApplicationStoreResolver))] - [InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictAuthorizationStoreResolver))] - [InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictScopeStoreResolver))] - [InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictTokenStoreResolver))] - public void AddCore_RegistersDefaultResolvers(Type serviceType, Type implementationType) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddCore(); - - // Assert - Assert.Contains(services, service => service.ServiceType == serviceType && - service.ImplementationType == implementationType); - } - - [Theory] - [InlineData(typeof(IOpenIddictApplicationManager))] - [InlineData(typeof(IOpenIddictAuthorizationManager))] - [InlineData(typeof(IOpenIddictScopeManager))] - [InlineData(typeof(IOpenIddictTokenManager))] - public void AddCore_RegistersUntypedProxies(Type type) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); + [Fact] + public void AddCore_RegistersOptionsServices() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); - // Act - builder.AddCore(); + // Act + builder.AddCore(); - // Assert - Assert.Contains(services, service => service.ServiceType == type && service.ImplementationFactory is not null); - } + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(IOptions<>)); + } - [Fact] - public void AddCore_ResolvingUntypedApplicationManagerThrowsAnExceptionWhenDefaultEntityIsNotSet() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); + [Theory] + [InlineData(typeof(OpenIddictApplicationManager<>))] + [InlineData(typeof(OpenIddictAuthorizationManager<>))] + [InlineData(typeof(OpenIddictScopeManager<>))] + [InlineData(typeof(OpenIddictTokenManager<>))] + public void AddCore_RegistersDefaultManagers(Type type) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddCore(); + + // Assert + Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type); + } + + [Theory] + [InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictApplicationStoreResolver))] + [InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictAuthorizationStoreResolver))] + [InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictScopeStoreResolver))] + [InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictTokenStoreResolver))] + public void AddCore_RegistersDefaultResolvers(Type serviceType, Type implementationType) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddCore(); + + // Assert + Assert.Contains(services, service => service.ServiceType == serviceType && + service.ImplementationType == implementationType); + } + + [Theory] + [InlineData(typeof(IOpenIddictApplicationManager))] + [InlineData(typeof(IOpenIddictAuthorizationManager))] + [InlineData(typeof(IOpenIddictScopeManager))] + [InlineData(typeof(IOpenIddictTokenManager))] + public void AddCore_RegistersUntypedProxies(Type type) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddCore(); - // Act - builder.AddCore(); + // Assert + Assert.Contains(services, service => service.ServiceType == type && service.ImplementationFactory is not null); + } - // Assert - var provider = services.BuildServiceProvider(); + [Fact] + public void AddCore_ResolvingUntypedApplicationManagerThrowsAnExceptionWhenDefaultEntityIsNotSet() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); - var exception = Assert.Throws(delegate - { - return provider.GetRequiredService(); - }); + // Act + builder.AddCore(); - Assert.Equal(SR.GetResourceString(SR.ID0273), exception.Message); - } + // Assert + var provider = services.BuildServiceProvider(); - [Fact] - public void AddCore_ResolvingUntypedAuthorizationManagerThrowsAnExceptionWhenDefaultEntityIsNotSet() + var exception = Assert.Throws(delegate { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); + return provider.GetRequiredService(); + }); - // Act - builder.AddCore(); + Assert.Equal(SR.GetResourceString(SR.ID0273), exception.Message); + } - // Assert - var provider = services.BuildServiceProvider(); + [Fact] + public void AddCore_ResolvingUntypedAuthorizationManagerThrowsAnExceptionWhenDefaultEntityIsNotSet() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); - var exception = Assert.Throws(delegate - { - return provider.GetRequiredService(); - }); + // Act + builder.AddCore(); - Assert.Equal(SR.GetResourceString(SR.ID0274), exception.Message); - } + // Assert + var provider = services.BuildServiceProvider(); - [Fact] - public void AddCore_ResolvingUntypedScopeManagerThrowsAnExceptionWhenDefaultEntityIsNotSet() + var exception = Assert.Throws(delegate { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); + return provider.GetRequiredService(); + }); - // Act - builder.AddCore(); + Assert.Equal(SR.GetResourceString(SR.ID0274), exception.Message); + } - // Assert - var provider = services.BuildServiceProvider(); + [Fact] + public void AddCore_ResolvingUntypedScopeManagerThrowsAnExceptionWhenDefaultEntityIsNotSet() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); - var exception = Assert.Throws(delegate - { - return provider.GetRequiredService(); - }); + // Act + builder.AddCore(); - Assert.Equal(SR.GetResourceString(SR.ID0275), exception.Message); - } + // Assert + var provider = services.BuildServiceProvider(); - [Fact] - public void AddCore_ResolvingUntypedTokenManagerThrowsAnExceptionWhenDefaultEntityIsNotSet() + var exception = Assert.Throws(delegate { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); + return provider.GetRequiredService(); + }); - // Act - builder.AddCore(); + Assert.Equal(SR.GetResourceString(SR.ID0275), exception.Message); + } - // Assert - var provider = services.BuildServiceProvider(); + [Fact] + public void AddCore_ResolvingUntypedTokenManagerThrowsAnExceptionWhenDefaultEntityIsNotSet() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); - var exception = Assert.Throws(delegate - { - return provider.GetRequiredService(); - }); + // Act + builder.AddCore(); - Assert.Equal(SR.GetResourceString(SR.ID0276), exception.Message); - } + // Assert + var provider = services.BuildServiceProvider(); - [Fact] - public void AddCore_ResolvingUntypedApplicationManagerReturnsGenericManager() + var exception = Assert.Throws(delegate { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddCore(options => - { - options.SetDefaultApplicationEntity(); - options.Services.AddSingleton(Mock.Of>()); - }); - - var provider = services.BuildServiceProvider(); - var manager = provider.GetRequiredService(); - - // Assert - Assert.IsType>(manager); - } - - [Fact] - public void AddCore_ResolvingUntypedAuthorizationManagerReturnsGenericManager() + return provider.GetRequiredService(); + }); + + Assert.Equal(SR.GetResourceString(SR.ID0276), exception.Message); + } + + [Fact] + public void AddCore_ResolvingUntypedApplicationManagerReturnsGenericManager() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddCore(options => { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddCore(options => - { - options.SetDefaultAuthorizationEntity(); - options.Services.AddSingleton(Mock.Of>()); - }); - - var provider = services.BuildServiceProvider(); - var manager = provider.GetRequiredService(); - - // Assert - Assert.IsType>(manager); - } - - [Fact] - public void AddCore_ResolvingUntypedScopeManagerReturnsGenericManager() + options.SetDefaultApplicationEntity(); + options.Services.AddSingleton(Mock.Of>()); + }); + + var provider = services.BuildServiceProvider(); + var manager = provider.GetRequiredService(); + + // Assert + Assert.IsType>(manager); + } + + [Fact] + public void AddCore_ResolvingUntypedAuthorizationManagerReturnsGenericManager() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddCore(options => { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddCore(options => - { - options.SetDefaultScopeEntity(); - options.Services.AddSingleton(Mock.Of>()); - }); - - var provider = services.BuildServiceProvider(); - var manager = provider.GetRequiredService(); - - // Assert - Assert.IsType>(manager); - } - - [Fact] - public void AddCore_ResolvingUntypedTokenManagerReturnsGenericManager() + options.SetDefaultAuthorizationEntity(); + options.Services.AddSingleton(Mock.Of>()); + }); + + var provider = services.BuildServiceProvider(); + var manager = provider.GetRequiredService(); + + // Assert + Assert.IsType>(manager); + } + + [Fact] + public void AddCore_ResolvingUntypedScopeManagerReturnsGenericManager() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddCore(options => { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddCore(options => - { - options.SetDefaultTokenEntity(); - options.Services.AddSingleton(Mock.Of>()); - }); - - var provider = services.BuildServiceProvider(); - var manager = provider.GetRequiredService(); - - // Assert - Assert.IsType>(manager); - } - - public class OpenIddictApplication { } - public class OpenIddictAuthorization { } - public class OpenIddictScope { } - public class OpenIddictToken { } + options.SetDefaultScopeEntity(); + options.Services.AddSingleton(Mock.Of>()); + }); + + var provider = services.BuildServiceProvider(); + var manager = provider.GetRequiredService(); + + // Assert + Assert.IsType>(manager); } + + [Fact] + public void AddCore_ResolvingUntypedTokenManagerReturnsGenericManager() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddCore(options => + { + options.SetDefaultTokenEntity(); + options.Services.AddSingleton(Mock.Of>()); + }); + + var provider = services.BuildServiceProvider(); + var manager = provider.GetRequiredService(); + + // Assert + Assert.IsType>(manager); + } + + public class OpenIddictApplication { } + public class OpenIddictAuthorization { } + public class OpenIddictScope { } + public class OpenIddictToken { } } diff --git a/test/OpenIddict.Core.Tests/Resolvers/OpenIddictApplicationStoreResolverTests.cs b/test/OpenIddict.Core.Tests/Resolvers/OpenIddictApplicationStoreResolverTests.cs index 44fc92f4..fb77907e 100644 --- a/test/OpenIddict.Core.Tests/Resolvers/OpenIddictApplicationStoreResolverTests.cs +++ b/test/OpenIddict.Core.Tests/Resolvers/OpenIddictApplicationStoreResolverTests.cs @@ -11,38 +11,37 @@ using OpenIddict.Abstractions; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core.Tests +namespace OpenIddict.Core.Tests; + +public class OpenIddictApplicationStoreResolverTests { - public class OpenIddictApplicationStoreResolverTests + [Fact] + public void Get_ThrowsAnExceptionWhenStoreCannotBeFound() { - [Fact] - public void Get_ThrowsAnExceptionWhenStoreCannotBeFound() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictApplicationStoreResolver(provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0228), exception.Message); - } - - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictApplicationStoreResolver(provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - public class OpenIddictApplication { } + // Arrange + var services = new ServiceCollection(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictApplicationStoreResolver(provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0228), exception.Message); } + + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictApplicationStoreResolver(provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + public class OpenIddictApplication { } } diff --git a/test/OpenIddict.Core.Tests/Resolvers/OpenIddictAuthorizationStoreResolverTests.cs b/test/OpenIddict.Core.Tests/Resolvers/OpenIddictAuthorizationStoreResolverTests.cs index 82ed3f21..dab40395 100644 --- a/test/OpenIddict.Core.Tests/Resolvers/OpenIddictAuthorizationStoreResolverTests.cs +++ b/test/OpenIddict.Core.Tests/Resolvers/OpenIddictAuthorizationStoreResolverTests.cs @@ -11,38 +11,37 @@ using OpenIddict.Abstractions; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core.Tests +namespace OpenIddict.Core.Tests; + +public class OpenIddictAuthorizationStoreResolverTests { - public class OpenIddictAuthorizationStoreResolverTests + [Fact] + public void Get_ThrowsAnExceptionWhenStoreCannotBeFound() { - [Fact] - public void Get_ThrowsAnExceptionWhenStoreCannotBeFound() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictAuthorizationStoreResolver(provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0229), exception.Message); - } - - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictAuthorizationStoreResolver(provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - public class OpenIddictAuthorization { } + // Arrange + var services = new ServiceCollection(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictAuthorizationStoreResolver(provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0229), exception.Message); } + + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictAuthorizationStoreResolver(provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + public class OpenIddictAuthorization { } } diff --git a/test/OpenIddict.Core.Tests/Resolvers/OpenIddictScopeStoreResolverTests.cs b/test/OpenIddict.Core.Tests/Resolvers/OpenIddictScopeStoreResolverTests.cs index 93ad00c0..4c8a6f63 100644 --- a/test/OpenIddict.Core.Tests/Resolvers/OpenIddictScopeStoreResolverTests.cs +++ b/test/OpenIddict.Core.Tests/Resolvers/OpenIddictScopeStoreResolverTests.cs @@ -11,38 +11,37 @@ using OpenIddict.Abstractions; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core.Tests +namespace OpenIddict.Core.Tests; + +public class OpenIddictScopeStoreResolverTests { - public class OpenIddictScopeStoreResolverTests + [Fact] + public void Get_ThrowsAnExceptionWhenStoreCannotBeFound() { - [Fact] - public void Get_ThrowsAnExceptionWhenStoreCannotBeFound() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictScopeStoreResolver(provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0230), exception.Message); - } - - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictScopeStoreResolver(provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - public class OpenIddictScope { } + // Arrange + var services = new ServiceCollection(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictScopeStoreResolver(provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0230), exception.Message); } + + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictScopeStoreResolver(provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + public class OpenIddictScope { } } diff --git a/test/OpenIddict.Core.Tests/Resolvers/OpenIddictTokenStoreResolverTests.cs b/test/OpenIddict.Core.Tests/Resolvers/OpenIddictTokenStoreResolverTests.cs index 813917b5..d12633c3 100644 --- a/test/OpenIddict.Core.Tests/Resolvers/OpenIddictTokenStoreResolverTests.cs +++ b/test/OpenIddict.Core.Tests/Resolvers/OpenIddictTokenStoreResolverTests.cs @@ -11,38 +11,37 @@ using OpenIddict.Abstractions; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Core.Tests +namespace OpenIddict.Core.Tests; + +public class OpenIddictTokenStoreResolverTests { - public class OpenIddictTokenStoreResolverTests + [Fact] + public void Get_ThrowsAnExceptionWhenStoreCannotBeFound() { - [Fact] - public void Get_ThrowsAnExceptionWhenStoreCannotBeFound() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictTokenStoreResolver(provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0231), exception.Message); - } - - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictTokenStoreResolver(provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - public class OpenIddictToken { } + // Arrange + var services = new ServiceCollection(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictTokenStoreResolver(provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0231), exception.Message); } + + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictTokenStoreResolver(provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + public class OpenIddictToken { } } diff --git a/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkBuilderTests.cs b/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkBuilderTests.cs index b4e0307e..8308bcb7 100644 --- a/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkBuilderTests.cs +++ b/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkBuilderTests.cs @@ -13,130 +13,129 @@ using OpenIddict.EntityFramework.Models; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework.Tests +namespace OpenIddict.EntityFramework.Tests; + +public class OpenIddictEntityFrameworkBuilderTests { - public class OpenIddictEntityFrameworkBuilderTests + [Fact] + public void Constructor_ThrowsAnExceptionForNullServices() { - [Fact] - public void Constructor_ThrowsAnExceptionForNullServices() - { - // Arrange - var services = (IServiceCollection) null!; + // Arrange + var services = (IServiceCollection) null!; - // Act and assert - var exception = Assert.Throws(() => new OpenIddictEntityFrameworkBuilder(services)); + // Act and assert + var exception = Assert.Throws(() => new OpenIddictEntityFrameworkBuilder(services)); - Assert.Equal("services", exception.ParamName); - } + Assert.Equal("services", exception.ParamName); + } - [Fact] - public void ReplaceDefaultEntities_EntitiesAreCorrectlyReplaced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceDefaultEntities_EntitiesAreCorrectlyReplaced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.ReplaceDefaultEntities(); + // Act + builder.ReplaceDefaultEntities(); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType); - Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType); - Assert.Equal(typeof(CustomScope), options.DefaultScopeType); - Assert.Equal(typeof(CustomToken), options.DefaultTokenType); - } + Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType); + Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType); + Assert.Equal(typeof(CustomScope), options.DefaultScopeType); + Assert.Equal(typeof(CustomToken), options.DefaultTokenType); + } - [Fact] - public void UseDbContext_ThrowsAnExceptionForNullType() + [Fact] + public void UseDbContext_ThrowsAnExceptionForNullType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + return builder.UseDbContext(type: null!); + }); - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.UseDbContext(type: null!); - }); + Assert.Equal("type", exception.ParamName); + } - Assert.Equal("type", exception.ParamName); - } + [Fact] + public void UseDbContext_ThrowsAnExceptionForInvalidType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void UseDbContext_ThrowsAnExceptionForInvalidType() + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.UseDbContext(typeof(object)); - }); - - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + return builder.UseDbContext(typeof(object)); + }); - [Fact] - public void UseDbContext_RegistersDbContextAsScopedService() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - // Act - builder.UseDbContext(); + [Fact] + public void UseDbContext_RegistersDbContextAsScopedService() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Contains(services, service => service.Lifetime == ServiceLifetime.Scoped && - service.ServiceType == typeof(CustomDbContext) && - service.ImplementationType == typeof(CustomDbContext)); - } + // Act + builder.UseDbContext(); - [Fact] - public void UseDbContext_SetsDbContextTypeInOptions() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Assert + Assert.Contains(services, service => service.Lifetime == ServiceLifetime.Scoped && + service.ServiceType == typeof(CustomDbContext) && + service.ImplementationType == typeof(CustomDbContext)); + } - // Act - builder.UseDbContext(); + [Fact] + public void UseDbContext_SetsDbContextTypeInOptions() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.UseDbContext(); - Assert.Equal(typeof(CustomDbContext), options.DbContextType); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - private static OpenIddictEntityFrameworkBuilder CreateBuilder(IServiceCollection services) - => services.AddOpenIddict().AddCore().UseEntityFramework(); + Assert.Equal(typeof(CustomDbContext), options.DbContextType); + } - private static IServiceCollection CreateServices() - { - var services = new ServiceCollection(); - services.AddOptions(); + private static OpenIddictEntityFrameworkBuilder CreateBuilder(IServiceCollection services) + => services.AddOpenIddict().AddCore().UseEntityFramework(); - return services; - } + private static IServiceCollection CreateServices() + { + var services = new ServiceCollection(); + services.AddOptions(); - public class CustomApplication : OpenIddictEntityFrameworkApplication { } - public class CustomAuthorization : OpenIddictEntityFrameworkAuthorization { } - public class CustomScope : OpenIddictEntityFrameworkScope { } - public class CustomToken : OpenIddictEntityFrameworkToken { } + return services; + } + + public class CustomApplication : OpenIddictEntityFrameworkApplication { } + public class CustomAuthorization : OpenIddictEntityFrameworkAuthorization { } + public class CustomScope : OpenIddictEntityFrameworkScope { } + public class CustomToken : OpenIddictEntityFrameworkToken { } - public class CustomDbContext : DbContext + public class CustomDbContext : DbContext + { + public CustomDbContext(string nameOrConnectionString) + : base(nameOrConnectionString) { - public CustomDbContext(string nameOrConnectionString) - : base(nameOrConnectionString) - { - } } } } diff --git a/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs b/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs index 8d97b051..6e533a1f 100644 --- a/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs +++ b/test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs @@ -12,109 +12,108 @@ using OpenIddict.Core; using OpenIddict.EntityFramework.Models; using Xunit; -namespace OpenIddict.EntityFramework.Tests +namespace OpenIddict.EntityFramework.Tests; + +public class OpenIddictEntityFrameworkExtensionsTests { - public class OpenIddictEntityFrameworkExtensionsTests + [Fact] + public void UseEntityFramework_ThrowsAnExceptionForNullBuilder() + { + // Arrange + var builder = (OpenIddictCoreBuilder) null!; + + // Act and assert + var exception = Assert.Throws(() => builder.UseEntityFramework()); + + Assert.Equal("builder", exception.ParamName); + } + + [Fact] + public void UseEntityFramework_ThrowsAnExceptionForNullConfiguration() { - [Fact] - public void UseEntityFramework_ThrowsAnExceptionForNullBuilder() - { - // Arrange - var builder = (OpenIddictCoreBuilder) null!; - - // Act and assert - var exception = Assert.Throws(() => builder.UseEntityFramework()); - - Assert.Equal("builder", exception.ParamName); - } - - [Fact] - public void UseEntityFramework_ThrowsAnExceptionForNullConfiguration() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.UseEntityFramework(configuration: null!)); - - Assert.Equal("configuration", exception.ParamName); - } - - [Fact] - public void UseEntityFramework_RegistersDefaultEntities() - { - // Arrange - var services = new ServiceCollection().AddOptions(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseEntityFramework(); - - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; - - Assert.Equal(typeof(OpenIddictEntityFrameworkApplication), options.DefaultApplicationType); - Assert.Equal(typeof(OpenIddictEntityFrameworkAuthorization), options.DefaultAuthorizationType); - Assert.Equal(typeof(OpenIddictEntityFrameworkScope), options.DefaultScopeType); - Assert.Equal(typeof(OpenIddictEntityFrameworkToken), options.DefaultTokenType); - } - - [Theory] - [InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictEntityFrameworkApplicationStoreResolver))] - [InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictEntityFrameworkAuthorizationStoreResolver))] - [InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictEntityFrameworkScopeStoreResolver))] - [InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictEntityFrameworkTokenStoreResolver))] - public void UseEntityFramework_RegistersEntityFrameworkStoreResolvers(Type serviceType, Type implementationType) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseEntityFramework(); - - // Assert - Assert.Contains(services, service => service.ServiceType == serviceType && - service.ImplementationType == implementationType); - } - - [Theory] - [InlineData(typeof(OpenIddictEntityFrameworkApplicationStoreResolver.TypeResolutionCache))] - [InlineData(typeof(OpenIddictEntityFrameworkAuthorizationStoreResolver.TypeResolutionCache))] - [InlineData(typeof(OpenIddictEntityFrameworkScopeStoreResolver.TypeResolutionCache))] - [InlineData(typeof(OpenIddictEntityFrameworkTokenStoreResolver.TypeResolutionCache))] - public void UseEntityFramework_RegistersEntityFrameworkStoreResolverCaches(Type type) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseEntityFramework(); - - // Assert - Assert.Contains(services, service => service.ServiceType == type && - service.ImplementationType == type); - } - - [Theory] - [InlineData(typeof(OpenIddictEntityFrameworkApplicationStore<,,,,>))] - [InlineData(typeof(OpenIddictEntityFrameworkAuthorizationStore<,,,,>))] - [InlineData(typeof(OpenIddictEntityFrameworkScopeStore<,,>))] - [InlineData(typeof(OpenIddictEntityFrameworkTokenStore<,,,,>))] - public void UseEntityFramework_RegistersEntityFrameworkStore(Type type) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseEntityFramework(); - - // Assert - Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type); - } + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.UseEntityFramework(configuration: null!)); + + Assert.Equal("configuration", exception.ParamName); + } + + [Fact] + public void UseEntityFramework_RegistersDefaultEntities() + { + // Arrange + var services = new ServiceCollection().AddOptions(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseEntityFramework(); + + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; + + Assert.Equal(typeof(OpenIddictEntityFrameworkApplication), options.DefaultApplicationType); + Assert.Equal(typeof(OpenIddictEntityFrameworkAuthorization), options.DefaultAuthorizationType); + Assert.Equal(typeof(OpenIddictEntityFrameworkScope), options.DefaultScopeType); + Assert.Equal(typeof(OpenIddictEntityFrameworkToken), options.DefaultTokenType); + } + + [Theory] + [InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictEntityFrameworkApplicationStoreResolver))] + [InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictEntityFrameworkAuthorizationStoreResolver))] + [InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictEntityFrameworkScopeStoreResolver))] + [InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictEntityFrameworkTokenStoreResolver))] + public void UseEntityFramework_RegistersEntityFrameworkStoreResolvers(Type serviceType, Type implementationType) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseEntityFramework(); + + // Assert + Assert.Contains(services, service => service.ServiceType == serviceType && + service.ImplementationType == implementationType); + } + + [Theory] + [InlineData(typeof(OpenIddictEntityFrameworkApplicationStoreResolver.TypeResolutionCache))] + [InlineData(typeof(OpenIddictEntityFrameworkAuthorizationStoreResolver.TypeResolutionCache))] + [InlineData(typeof(OpenIddictEntityFrameworkScopeStoreResolver.TypeResolutionCache))] + [InlineData(typeof(OpenIddictEntityFrameworkTokenStoreResolver.TypeResolutionCache))] + public void UseEntityFramework_RegistersEntityFrameworkStoreResolverCaches(Type type) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseEntityFramework(); + + // Assert + Assert.Contains(services, service => service.ServiceType == type && + service.ImplementationType == type); + } + + [Theory] + [InlineData(typeof(OpenIddictEntityFrameworkApplicationStore<,,,,>))] + [InlineData(typeof(OpenIddictEntityFrameworkAuthorizationStore<,,,,>))] + [InlineData(typeof(OpenIddictEntityFrameworkScopeStore<,,>))] + [InlineData(typeof(OpenIddictEntityFrameworkTokenStore<,,,,>))] + public void UseEntityFramework_RegistersEntityFrameworkStore(Type type) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseEntityFramework(); + + // Assert + Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type); } } diff --git a/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolverTests.cs b/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolverTests.cs index 30399275..61acef4f 100644 --- a/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolverTests.cs +++ b/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolverTests.cs @@ -16,94 +16,93 @@ using Xunit; using static OpenIddict.EntityFramework.OpenIddictEntityFrameworkApplicationStoreResolver; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework.Tests +namespace OpenIddict.EntityFramework.Tests; + +public class OpenIddictEntityFrameworkApplicationStoreResolverTests { - public class OpenIddictEntityFrameworkApplicationStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0234), exception.Message); + } + + [Fact] + public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0234), exception.Message); - } - - [Fact] - public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions - { - DbContextType = null - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message); - } - - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions - { - DbContextType = typeof(DbContext) - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - private static OpenIddictEntityFrameworkApplicationStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of(), - Mock.Of>()).Object; - - public class CustomApplication { } - - public class MyApplication : OpenIddictEntityFrameworkApplication { } - public class MyAuthorization : OpenIddictEntityFrameworkAuthorization { } - public class MyScope : OpenIddictEntityFrameworkScope { } - public class MyToken : OpenIddictEntityFrameworkToken { } + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions + { + DbContextType = null + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message); } + + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions + { + DbContextType = typeof(DbContext) + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + private static OpenIddictEntityFrameworkApplicationStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of(), + Mock.Of>()).Object; + + public class CustomApplication { } + + public class MyApplication : OpenIddictEntityFrameworkApplication { } + public class MyAuthorization : OpenIddictEntityFrameworkAuthorization { } + public class MyScope : OpenIddictEntityFrameworkScope { } + public class MyToken : OpenIddictEntityFrameworkToken { } } diff --git a/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolverTests.cs b/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolverTests.cs index 51f4c52a..fbc13807 100644 --- a/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolverTests.cs +++ b/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolverTests.cs @@ -16,94 +16,93 @@ using Xunit; using static OpenIddict.EntityFramework.OpenIddictEntityFrameworkAuthorizationStoreResolver; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework.Tests +namespace OpenIddict.EntityFramework.Tests; + +public class OpenIddictEntityFrameworkAuthorizationStoreResolverTests { - public class OpenIddictEntityFrameworkAuthorizationStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0236), exception.Message); + } + + [Fact] + public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0236), exception.Message); - } - - [Fact] - public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions - { - DbContextType = null - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message); - } - - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions - { - DbContextType = typeof(DbContext) - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - private static OpenIddictEntityFrameworkAuthorizationStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of(), - Mock.Of>()).Object; - - public class CustomAuthorization { } - - public class MyApplication : OpenIddictEntityFrameworkApplication { } - public class MyAuthorization : OpenIddictEntityFrameworkAuthorization { } - public class MyScope : OpenIddictEntityFrameworkScope { } - public class MyToken : OpenIddictEntityFrameworkToken { } + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions + { + DbContextType = null + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message); } + + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions + { + DbContextType = typeof(DbContext) + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + private static OpenIddictEntityFrameworkAuthorizationStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of(), + Mock.Of>()).Object; + + public class CustomAuthorization { } + + public class MyApplication : OpenIddictEntityFrameworkApplication { } + public class MyAuthorization : OpenIddictEntityFrameworkAuthorization { } + public class MyScope : OpenIddictEntityFrameworkScope { } + public class MyToken : OpenIddictEntityFrameworkToken { } } diff --git a/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkScopeStoreResolverTests.cs b/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkScopeStoreResolverTests.cs index eae12890..68b6d3b7 100644 --- a/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkScopeStoreResolverTests.cs +++ b/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkScopeStoreResolverTests.cs @@ -16,94 +16,93 @@ using Xunit; using static OpenIddict.EntityFramework.OpenIddictEntityFrameworkScopeStoreResolver; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework.Tests +namespace OpenIddict.EntityFramework.Tests; + +public class OpenIddictEntityFrameworkScopeStoreResolverTests { - public class OpenIddictEntityFrameworkScopeStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0237), exception.Message); + } + + [Fact] + public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0237), exception.Message); - } - - [Fact] - public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions - { - DbContextType = null - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message); - } - - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions - { - DbContextType = typeof(DbContext) - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - private static OpenIddictEntityFrameworkScopeStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of(), - Mock.Of>()).Object; - - public class CustomScope { } - - public class MyApplication : OpenIddictEntityFrameworkApplication { } - public class MyAuthorization : OpenIddictEntityFrameworkAuthorization { } - public class MyScope : OpenIddictEntityFrameworkScope { } - public class MyToken : OpenIddictEntityFrameworkToken { } + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions + { + DbContextType = null + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message); } + + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions + { + DbContextType = typeof(DbContext) + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + private static OpenIddictEntityFrameworkScopeStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of(), + Mock.Of>()).Object; + + public class CustomScope { } + + public class MyApplication : OpenIddictEntityFrameworkApplication { } + public class MyAuthorization : OpenIddictEntityFrameworkAuthorization { } + public class MyScope : OpenIddictEntityFrameworkScope { } + public class MyToken : OpenIddictEntityFrameworkToken { } } diff --git a/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkTokenStoreResolverTests.cs b/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkTokenStoreResolverTests.cs index 89f3862f..7eeacca1 100644 --- a/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkTokenStoreResolverTests.cs +++ b/test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkTokenStoreResolverTests.cs @@ -16,94 +16,93 @@ using Xunit; using static OpenIddict.EntityFramework.OpenIddictEntityFrameworkTokenStoreResolver; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFramework.Tests +namespace OpenIddict.EntityFramework.Tests; + +public class OpenIddictEntityFrameworkTokenStoreResolverTests { - public class OpenIddictEntityFrameworkTokenStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0238), exception.Message); + } + + [Fact] + public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0238), exception.Message); - } - - [Fact] - public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions - { - DbContextType = null - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message); - } - - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions - { - DbContextType = typeof(DbContext) - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - private static OpenIddictEntityFrameworkTokenStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of(), - Mock.Of>()).Object; - - public class CustomToken { } - - public class MyApplication : OpenIddictEntityFrameworkApplication { } - public class MyAuthorization : OpenIddictEntityFrameworkAuthorization { } - public class MyScope : OpenIddictEntityFrameworkScope { } - public class MyToken : OpenIddictEntityFrameworkToken { } + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions + { + DbContextType = null + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message); } + + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions + { + DbContextType = typeof(DbContext) + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + private static OpenIddictEntityFrameworkTokenStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of(), + Mock.Of>()).Object; + + public class CustomToken { } + + public class MyApplication : OpenIddictEntityFrameworkApplication { } + public class MyAuthorization : OpenIddictEntityFrameworkAuthorization { } + public class MyScope : OpenIddictEntityFrameworkScope { } + public class MyToken : OpenIddictEntityFrameworkToken { } } diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreBuilderTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreBuilderTests.cs index 022263d5..87f26ecf 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreBuilderTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreBuilderTests.cs @@ -13,134 +13,133 @@ using OpenIddict.EntityFrameworkCore.Models; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore.Tests +namespace OpenIddict.EntityFrameworkCore.Tests; + +public class OpenIddictEntityFrameworkCoreBuilderTests { - public class OpenIddictEntityFrameworkCoreBuilderTests + [Fact] + public void Constructor_ThrowsAnExceptionForNullServices() { - [Fact] - public void Constructor_ThrowsAnExceptionForNullServices() - { - // Arrange - var services = (IServiceCollection) null!; + // Arrange + var services = (IServiceCollection) null!; - // Act and assert - var exception = Assert.Throws(() => new OpenIddictEntityFrameworkCoreBuilder(services)); + // Act and assert + var exception = Assert.Throws(() => new OpenIddictEntityFrameworkCoreBuilder(services)); - Assert.Equal("services", exception.ParamName); - } + Assert.Equal("services", exception.ParamName); + } - [Fact] - public void ReplaceDefaultEntities_EntitiesAreCorrectlyReplaced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceDefaultEntities_EntitiesAreCorrectlyReplaced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.ReplaceDefaultEntities(); + // Act + builder.ReplaceDefaultEntities(); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType); - Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType); - Assert.Equal(typeof(CustomScope), options.DefaultScopeType); - Assert.Equal(typeof(CustomToken), options.DefaultTokenType); - } + Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType); + Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType); + Assert.Equal(typeof(CustomScope), options.DefaultScopeType); + Assert.Equal(typeof(CustomToken), options.DefaultTokenType); + } - [Fact] - public void ReplaceDefaultEntities_AllowsSpecifyingCustomKeyType() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void ReplaceDefaultEntities_AllowsSpecifyingCustomKeyType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.ReplaceDefaultEntities(); + // Act + builder.ReplaceDefaultEntities(); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - Assert.Equal(typeof(OpenIddictEntityFrameworkCoreApplication), options.DefaultApplicationType); - Assert.Equal(typeof(OpenIddictEntityFrameworkCoreAuthorization), options.DefaultAuthorizationType); - Assert.Equal(typeof(OpenIddictEntityFrameworkCoreScope), options.DefaultScopeType); - Assert.Equal(typeof(OpenIddictEntityFrameworkCoreToken), options.DefaultTokenType); - } + Assert.Equal(typeof(OpenIddictEntityFrameworkCoreApplication), options.DefaultApplicationType); + Assert.Equal(typeof(OpenIddictEntityFrameworkCoreAuthorization), options.DefaultAuthorizationType); + Assert.Equal(typeof(OpenIddictEntityFrameworkCoreScope), options.DefaultScopeType); + Assert.Equal(typeof(OpenIddictEntityFrameworkCoreToken), options.DefaultTokenType); + } - [Fact] - public void UseDbContext_ThrowsAnExceptionForNullType() + [Fact] + public void UseDbContext_ThrowsAnExceptionForNullType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + return builder.UseDbContext(type: null!); + }); - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.UseDbContext(type: null!); - }); + Assert.Equal("type", exception.ParamName); + } - Assert.Equal("type", exception.ParamName); - } + [Fact] + public void UseDbContext_ThrowsAnExceptionForInvalidType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void UseDbContext_ThrowsAnExceptionForInvalidType() + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.UseDbContext(typeof(object)); - }); - - Assert.Equal("type", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); - } + return builder.UseDbContext(typeof(object)); + }); - [Fact] - public void UseDbContext_SetsDbContextTypeInOptions() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal("type", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message); + } - // Act - builder.UseDbContext(); + [Fact] + public void UseDbContext_SetsDbContextTypeInOptions() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.UseDbContext(); - Assert.Equal(typeof(CustomDbContext), options.DbContextType); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - private static OpenIddictEntityFrameworkCoreBuilder CreateBuilder(IServiceCollection services) - => services.AddOpenIddict().AddCore().UseEntityFrameworkCore(); + Assert.Equal(typeof(CustomDbContext), options.DbContextType); + } - private static IServiceCollection CreateServices() - { - var services = new ServiceCollection(); - services.AddOptions(); + private static OpenIddictEntityFrameworkCoreBuilder CreateBuilder(IServiceCollection services) + => services.AddOpenIddict().AddCore().UseEntityFrameworkCore(); - return services; - } + private static IServiceCollection CreateServices() + { + var services = new ServiceCollection(); + services.AddOptions(); - public class CustomApplication : OpenIddictEntityFrameworkCoreApplication { } - public class CustomAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } - public class CustomScope : OpenIddictEntityFrameworkCoreScope { } - public class CustomToken : OpenIddictEntityFrameworkCoreToken { } + return services; + } + + public class CustomApplication : OpenIddictEntityFrameworkCoreApplication { } + public class CustomAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } + public class CustomScope : OpenIddictEntityFrameworkCoreScope { } + public class CustomToken : OpenIddictEntityFrameworkCoreToken { } - public class CustomDbContext : DbContext + public class CustomDbContext : DbContext + { + public CustomDbContext(DbContextOptions options) + : base(options) { - public CustomDbContext(DbContextOptions options) - : base(options) - { - } } } } diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs index 7d907c85..aaee7706 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs @@ -12,109 +12,108 @@ using OpenIddict.Core; using OpenIddict.EntityFrameworkCore.Models; using Xunit; -namespace OpenIddict.EntityFrameworkCore.Tests +namespace OpenIddict.EntityFrameworkCore.Tests; + +public class OpenIddictEntityFrameworkCoreExtensionsTests { - public class OpenIddictEntityFrameworkCoreExtensionsTests + [Fact] + public void UseEntityFrameworkCore_ThrowsAnExceptionForNullBuilder() + { + // Arrange + var builder = (OpenIddictCoreBuilder) null!; + + // Act and assert + var exception = Assert.Throws(() => builder.UseEntityFrameworkCore()); + + Assert.Equal("builder", exception.ParamName); + } + + [Fact] + public void UseEntityFrameworkCore_ThrowsAnExceptionForNullConfiguration() { - [Fact] - public void UseEntityFrameworkCore_ThrowsAnExceptionForNullBuilder() - { - // Arrange - var builder = (OpenIddictCoreBuilder) null!; - - // Act and assert - var exception = Assert.Throws(() => builder.UseEntityFrameworkCore()); - - Assert.Equal("builder", exception.ParamName); - } - - [Fact] - public void UseEntityFrameworkCore_ThrowsAnExceptionForNullConfiguration() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.UseEntityFrameworkCore(configuration: null!)); - - Assert.Equal("configuration", exception.ParamName); - } - - [Fact] - public void UseEntityFrameworkCore_RegistersDefaultEntities() - { - // Arrange - var services = new ServiceCollection().AddOptions(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseEntityFrameworkCore(); - - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; - - Assert.Equal(typeof(OpenIddictEntityFrameworkCoreApplication), options.DefaultApplicationType); - Assert.Equal(typeof(OpenIddictEntityFrameworkCoreAuthorization), options.DefaultAuthorizationType); - Assert.Equal(typeof(OpenIddictEntityFrameworkCoreScope), options.DefaultScopeType); - Assert.Equal(typeof(OpenIddictEntityFrameworkCoreToken), options.DefaultTokenType); - } - - [Theory] - [InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictEntityFrameworkCoreApplicationStoreResolver))] - [InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictEntityFrameworkCoreAuthorizationStoreResolver))] - [InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictEntityFrameworkCoreScopeStoreResolver))] - [InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictEntityFrameworkCoreTokenStoreResolver))] - public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStoreResolvers(Type serviceType, Type implementationType) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseEntityFrameworkCore(); - - // Assert - Assert.Contains(services, service => service.ServiceType == serviceType && - service.ImplementationType == implementationType); - } - - [Theory] - [InlineData(typeof(OpenIddictEntityFrameworkCoreApplicationStoreResolver.TypeResolutionCache))] - [InlineData(typeof(OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.TypeResolutionCache))] - [InlineData(typeof(OpenIddictEntityFrameworkCoreScopeStoreResolver.TypeResolutionCache))] - [InlineData(typeof(OpenIddictEntityFrameworkCoreTokenStoreResolver.TypeResolutionCache))] - public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStoreResolverCaches(Type type) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseEntityFrameworkCore(); - - // Assert - Assert.Contains(services, service => service.ServiceType == type && - service.ImplementationType == type); - } - - [Theory] - [InlineData(typeof(OpenIddictEntityFrameworkCoreApplicationStore<,,,,>))] - [InlineData(typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<,,,,>))] - [InlineData(typeof(OpenIddictEntityFrameworkCoreScopeStore<,,>))] - [InlineData(typeof(OpenIddictEntityFrameworkCoreTokenStore<,,,,>))] - public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStore(Type type) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseEntityFrameworkCore(); - - // Assert - Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type); - } + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.UseEntityFrameworkCore(configuration: null!)); + + Assert.Equal("configuration", exception.ParamName); + } + + [Fact] + public void UseEntityFrameworkCore_RegistersDefaultEntities() + { + // Arrange + var services = new ServiceCollection().AddOptions(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseEntityFrameworkCore(); + + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; + + Assert.Equal(typeof(OpenIddictEntityFrameworkCoreApplication), options.DefaultApplicationType); + Assert.Equal(typeof(OpenIddictEntityFrameworkCoreAuthorization), options.DefaultAuthorizationType); + Assert.Equal(typeof(OpenIddictEntityFrameworkCoreScope), options.DefaultScopeType); + Assert.Equal(typeof(OpenIddictEntityFrameworkCoreToken), options.DefaultTokenType); + } + + [Theory] + [InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictEntityFrameworkCoreApplicationStoreResolver))] + [InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictEntityFrameworkCoreAuthorizationStoreResolver))] + [InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictEntityFrameworkCoreScopeStoreResolver))] + [InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictEntityFrameworkCoreTokenStoreResolver))] + public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStoreResolvers(Type serviceType, Type implementationType) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseEntityFrameworkCore(); + + // Assert + Assert.Contains(services, service => service.ServiceType == serviceType && + service.ImplementationType == implementationType); + } + + [Theory] + [InlineData(typeof(OpenIddictEntityFrameworkCoreApplicationStoreResolver.TypeResolutionCache))] + [InlineData(typeof(OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.TypeResolutionCache))] + [InlineData(typeof(OpenIddictEntityFrameworkCoreScopeStoreResolver.TypeResolutionCache))] + [InlineData(typeof(OpenIddictEntityFrameworkCoreTokenStoreResolver.TypeResolutionCache))] + public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStoreResolverCaches(Type type) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseEntityFrameworkCore(); + + // Assert + Assert.Contains(services, service => service.ServiceType == type && + service.ImplementationType == type); + } + + [Theory] + [InlineData(typeof(OpenIddictEntityFrameworkCoreApplicationStore<,,,,>))] + [InlineData(typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<,,,,>))] + [InlineData(typeof(OpenIddictEntityFrameworkCoreScopeStore<,,>))] + [InlineData(typeof(OpenIddictEntityFrameworkCoreTokenStore<,,,,>))] + public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStore(Type type) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseEntityFrameworkCore(); + + // Assert + Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type); } } diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreHelpersTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreHelpersTests.cs index 8424e22f..a0585236 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreHelpersTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreHelpersTests.cs @@ -11,97 +11,96 @@ using Moq; using OpenIddict.EntityFrameworkCore.Models; using Xunit; -namespace OpenIddict.EntityFrameworkCore.Tests +namespace OpenIddict.EntityFrameworkCore.Tests; + +public class OpenIddictEntityFrameworkCoreHelpersTests { - public class OpenIddictEntityFrameworkCoreHelpersTests + [Fact] + public void UseOpenIddict_RegistersDefaultEntityConfigurations() { - [Fact] - public void UseOpenIddict_RegistersDefaultEntityConfigurations() - { - // Arrange - var builder = new Mock(new ConventionSet()); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) - .Returns(builder.Object); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) - .Returns(builder.Object); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) - .Returns(builder.Object); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) - .Returns(builder.Object); - - // Act - builder.Object.UseOpenIddict(); + // Arrange + var builder = new Mock(new ConventionSet()); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) + .Returns(builder.Object); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) + .Returns(builder.Object); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) + .Returns(builder.Object); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) + .Returns(builder.Object); - // Assert - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny>()), Times.Once()); - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny>()), Times.Once()); - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny>()), Times.Once()); - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny>()), Times.Once()); - } + // Act + builder.Object.UseOpenIddict(); - [Fact] - public void UseOpenIddict_RegistersDefaultEntityConfigurationsWithCustomKeyType() - { - // Arrange - var builder = new Mock(new ConventionSet()); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>>())) - .Returns(builder.Object); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>>())) - .Returns(builder.Object); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>>())) - .Returns(builder.Object); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>>())) - .Returns(builder.Object); + // Assert + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny>()), Times.Once()); + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny>()), Times.Once()); + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny>()), Times.Once()); + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny>()), Times.Once()); + } - // Act - builder.Object.UseOpenIddict(); + [Fact] + public void UseOpenIddict_RegistersDefaultEntityConfigurationsWithCustomKeyType() + { + // Arrange + var builder = new Mock(new ConventionSet()); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>>())) + .Returns(builder.Object); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>>())) + .Returns(builder.Object); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>>())) + .Returns(builder.Object); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>>())) + .Returns(builder.Object); - // Assert - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny, OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreToken, long>>()), Times.Once()); - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreToken, long>>()), Times.Once()); - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny, long>>()), Times.Once()); - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreAuthorization, long>>()), Times.Once()); - } + // Act + builder.Object.UseOpenIddict(); - [Fact] - public void UseOpenIddict_RegistersCustomEntityConfigurations() - { - // Arrange - var builder = new Mock(new ConventionSet()); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) - .Returns(builder.Object); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) - .Returns(builder.Object); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) - .Returns(builder.Object); - builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) - .Returns(builder.Object); + // Assert + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny, OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreToken, long>>()), Times.Once()); + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreToken, long>>()), Times.Once()); + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny, long>>()), Times.Once()); + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreAuthorization, long>>()), Times.Once()); + } - // Act - builder.Object.UseOpenIddict(); + [Fact] + public void UseOpenIddict_RegistersCustomEntityConfigurations() + { + // Arrange + var builder = new Mock(new ConventionSet()); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) + .Returns(builder.Object); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) + .Returns(builder.Object); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) + .Returns(builder.Object); + builder.Setup(mock => mock.ApplyConfiguration(It.IsAny>())) + .Returns(builder.Object); - // Assert - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny>()), Times.Once()); - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny>()), Times.Once()); - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny>()), Times.Once()); - builder.Verify(mock => mock.ApplyConfiguration( - It.IsAny>()), Times.Once()); - } + // Act + builder.Object.UseOpenIddict(); - public class CustomApplication : OpenIddictEntityFrameworkCoreApplication { } - public class CustomAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } - public class CustomScope : OpenIddictEntityFrameworkCoreScope { } - public class CustomToken : OpenIddictEntityFrameworkCoreToken { } + // Assert + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny>()), Times.Once()); + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny>()), Times.Once()); + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny>()), Times.Once()); + builder.Verify(mock => mock.ApplyConfiguration( + It.IsAny>()), Times.Once()); } + + public class CustomApplication : OpenIddictEntityFrameworkCoreApplication { } + public class CustomAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } + public class CustomScope : OpenIddictEntityFrameworkCoreScope { } + public class CustomToken : OpenIddictEntityFrameworkCoreToken { } } diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolverTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolverTests.cs index 94039a9c..0fb44c7e 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolverTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolverTests.cs @@ -16,94 +16,93 @@ using Xunit; using static OpenIddict.EntityFrameworkCore.OpenIddictEntityFrameworkCoreApplicationStoreResolver; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore.Tests +namespace OpenIddict.EntityFrameworkCore.Tests; + +public class OpenIddictEntityFrameworkCoreApplicationStoreResolverTests { - public class OpenIddictEntityFrameworkCoreApplicationStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreApplicationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreApplicationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0252), exception.Message); + } + + [Fact] + public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreApplicationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreApplicationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0252), exception.Message); - } - - [Fact] - public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions - { - DbContextType = null - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreApplicationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0253), exception.Message); - } - - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions - { - DbContextType = typeof(DbContext) - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreApplicationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - private static OpenIddictEntityFrameworkCoreApplicationStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of(), - Mock.Of>()).Object; - - public class CustomApplication { } - - public class MyApplication : OpenIddictEntityFrameworkCoreApplication { } - public class MyAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } - public class MyScope : OpenIddictEntityFrameworkCoreScope { } - public class MyToken : OpenIddictEntityFrameworkCoreToken { } + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions + { + DbContextType = null + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreApplicationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0253), exception.Message); } + + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions + { + DbContextType = typeof(DbContext) + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreApplicationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + private static OpenIddictEntityFrameworkCoreApplicationStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of(), + Mock.Of>()).Object; + + public class CustomApplication { } + + public class MyApplication : OpenIddictEntityFrameworkCoreApplication { } + public class MyAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } + public class MyScope : OpenIddictEntityFrameworkCoreScope { } + public class MyToken : OpenIddictEntityFrameworkCoreToken { } } diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolverTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolverTests.cs index 6a352033..7fc40820 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolverTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolverTests.cs @@ -16,94 +16,93 @@ using Xunit; using static OpenIddict.EntityFrameworkCore.OpenIddictEntityFrameworkCoreAuthorizationStoreResolver; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore.Tests +namespace OpenIddict.EntityFrameworkCore.Tests; + +public class OpenIddictEntityFrameworkCoreAuthorizationStoreResolverTests { - public class OpenIddictEntityFrameworkCoreAuthorizationStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0254), exception.Message); + } + + [Fact] + public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0254), exception.Message); - } - - [Fact] - public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions - { - DbContextType = null - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0253), exception.Message); - } - - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions - { - DbContextType = typeof(DbContext) - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - private static OpenIddictEntityFrameworkCoreAuthorizationStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of(), - Mock.Of>()).Object; - - public class CustomAuthorization { } - - public class MyApplication : OpenIddictEntityFrameworkCoreApplication { } - public class MyAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } - public class MyScope : OpenIddictEntityFrameworkCoreScope { } - public class MyToken : OpenIddictEntityFrameworkCoreToken { } + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions + { + DbContextType = null + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0253), exception.Message); } + + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions + { + DbContextType = typeof(DbContext) + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreAuthorizationStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + private static OpenIddictEntityFrameworkCoreAuthorizationStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of(), + Mock.Of>()).Object; + + public class CustomAuthorization { } + + public class MyApplication : OpenIddictEntityFrameworkCoreApplication { } + public class MyAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } + public class MyScope : OpenIddictEntityFrameworkCoreScope { } + public class MyToken : OpenIddictEntityFrameworkCoreToken { } } diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolverTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolverTests.cs index 62f79ca4..c9105e58 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolverTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolverTests.cs @@ -16,94 +16,93 @@ using Xunit; using static OpenIddict.EntityFrameworkCore.OpenIddictEntityFrameworkCoreScopeStoreResolver; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore.Tests +namespace OpenIddict.EntityFrameworkCore.Tests; + +public class OpenIddictEntityFrameworkCoreScopeStoreResolverTests { - public class OpenIddictEntityFrameworkCoreScopeStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreScopeStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreScopeStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0255), exception.Message); + } + + [Fact] + public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreScopeStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreScopeStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0255), exception.Message); - } - - [Fact] - public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions - { - DbContextType = null - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreScopeStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0253), exception.Message); - } - - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions - { - DbContextType = typeof(DbContext) - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreScopeStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - private static OpenIddictEntityFrameworkCoreScopeStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of(), - Mock.Of>()).Object; - - public class CustomScope { } - - public class MyApplication : OpenIddictEntityFrameworkCoreApplication { } - public class MyAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } - public class MyScope : OpenIddictEntityFrameworkCoreScope { } - public class MyToken : OpenIddictEntityFrameworkCoreToken { } + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions + { + DbContextType = null + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreScopeStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0253), exception.Message); } + + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions + { + DbContextType = typeof(DbContext) + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreScopeStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + private static OpenIddictEntityFrameworkCoreScopeStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of(), + Mock.Of>()).Object; + + public class CustomScope { } + + public class MyApplication : OpenIddictEntityFrameworkCoreApplication { } + public class MyAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } + public class MyScope : OpenIddictEntityFrameworkCoreScope { } + public class MyToken : OpenIddictEntityFrameworkCoreToken { } } diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolverTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolverTests.cs index 9766a65d..b6836d0f 100644 --- a/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolverTests.cs +++ b/test/OpenIddict.EntityFrameworkCore.Tests/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolverTests.cs @@ -16,94 +16,93 @@ using Xunit; using static OpenIddict.EntityFrameworkCore.OpenIddictEntityFrameworkCoreTokenStoreResolver; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.EntityFrameworkCore.Tests +namespace OpenIddict.EntityFrameworkCore.Tests; + +public class OpenIddictEntityFrameworkCoreTokenStoreResolverTests { - public class OpenIddictEntityFrameworkCoreTokenStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreTokenStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>(); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreTokenStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0256), exception.Message); + } + + [Fact] + public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreTokenStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreTokenStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0256), exception.Message); - } - - [Fact] - public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable() - { - // Arrange - var services = new ServiceCollection(); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions - { - DbContextType = null - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreTokenStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); - - Assert.Equal(SR.GetResourceString(SR.ID0253), exception.Message); - } - - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions - { - DbContextType = typeof(DbContext) - }); - - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictEntityFrameworkCoreTokenStoreResolver(new TypeResolutionCache(), options, provider); - - // Act and assert - Assert.NotNull(resolver.Get()); - } - - private static OpenIddictEntityFrameworkCoreTokenStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of(), - Mock.Of>()).Object; - - public class CustomToken { } - - public class MyApplication : OpenIddictEntityFrameworkCoreApplication { } - public class MyAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } - public class MyScope : OpenIddictEntityFrameworkCoreScope { } - public class MyToken : OpenIddictEntityFrameworkCoreToken { } + // Arrange + var services = new ServiceCollection(); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions + { + DbContextType = null + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreTokenStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); + + Assert.Equal(SR.GetResourceString(SR.ID0253), exception.Message); } + + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictEntityFrameworkCoreOptions + { + DbContextType = typeof(DbContext) + }); + + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictEntityFrameworkCoreTokenStoreResolver(new TypeResolutionCache(), options, provider); + + // Act and assert + Assert.NotNull(resolver.Get()); + } + + private static OpenIddictEntityFrameworkCoreTokenStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of(), + Mock.Of>()).Object; + + public class CustomToken { } + + public class MyApplication : OpenIddictEntityFrameworkCoreApplication { } + public class MyAuthorization : OpenIddictEntityFrameworkCoreAuthorization { } + public class MyScope : OpenIddictEntityFrameworkCoreScope { } + public class MyToken : OpenIddictEntityFrameworkCoreToken { } } diff --git a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbBuilderTests.cs b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbBuilderTests.cs index 213649aa..1350e50f 100644 --- a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbBuilderTests.cs +++ b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbBuilderTests.cs @@ -14,270 +14,269 @@ using OpenIddict.MongoDb.Models; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb.Tests +namespace OpenIddict.MongoDb.Tests; + +public class OpenIddictMongoDbBuilderTests { - public class OpenIddictMongoDbBuilderTests + [Fact] + public void Constructor_ThrowsAnExceptionForNullServices() { - [Fact] - public void Constructor_ThrowsAnExceptionForNullServices() - { - // Arrange - var services = (IServiceCollection) null!; + // Arrange + var services = (IServiceCollection) null!; - // Act and assert - var exception = Assert.Throws(() => new OpenIddictMongoDbBuilder(services)); + // Act and assert + var exception = Assert.Throws(() => new OpenIddictMongoDbBuilder(services)); - Assert.Equal("services", exception.ParamName); - } - - [Fact] - public void ReplaceDefaultApplicationEntity_EntityIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal("services", exception.ParamName); + } - // Act - builder.ReplaceDefaultApplicationEntity(); + [Fact] + public void ReplaceDefaultApplicationEntity_EntityIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.ReplaceDefaultApplicationEntity(); - Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - [Fact] - public void ReplaceDefaultAuthorizationEntity_EntityIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType); + } - // Act - builder.ReplaceDefaultAuthorizationEntity(); + [Fact] + public void ReplaceDefaultAuthorizationEntity_EntityIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.ReplaceDefaultAuthorizationEntity(); - Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - [Fact] - public void ReplaceDefaultScopeEntity_EntityIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType); + } - // Act - builder.ReplaceDefaultScopeEntity(); + [Fact] + public void ReplaceDefaultScopeEntity_EntityIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.ReplaceDefaultScopeEntity(); - Assert.Equal(typeof(CustomScope), options.DefaultScopeType); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - [Fact] - public void ReplaceDefaultTokenEntity_EntityIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.ReplaceDefaultTokenEntity(); + Assert.Equal(typeof(CustomScope), options.DefaultScopeType); + } - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + [Fact] + public void ReplaceDefaultTokenEntity_EntityIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - Assert.Equal(typeof(CustomToken), options.DefaultTokenType); - } + // Act + builder.ReplaceDefaultTokenEntity(); - [Theory] - [InlineData(null)] - [InlineData("")] - public void SetApplicationsCollectionName_ThrowsAnExceptionForNullOrEmptyCollectionName(string name) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - // Act and assert - var exception = Assert.Throws(() => builder.SetApplicationsCollectionName(name)); + Assert.Equal(typeof(CustomToken), options.DefaultTokenType); + } - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0261), exception.Message); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetApplicationsCollectionName_ThrowsAnExceptionForNullOrEmptyCollectionName(string name) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void SetApplicationsCollectionName_CollectionNameIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Act and assert + var exception = Assert.Throws(() => builder.SetApplicationsCollectionName(name)); - // Act - builder.SetApplicationsCollectionName("custom_collection"); + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0261), exception.Message); + } - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + [Fact] + public void SetApplicationsCollectionName_CollectionNameIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - Assert.Equal("custom_collection", options.ApplicationsCollectionName); - } + // Act + builder.SetApplicationsCollectionName("custom_collection"); - [Theory] - [InlineData(null)] - [InlineData("")] - public void SetAuthorizationsCollectionName_ThrowsAnExceptionForNullOrEmptyCollectionName(string name) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - // Act and assert - var exception = Assert.Throws(() => builder.SetAuthorizationsCollectionName(name)); + Assert.Equal("custom_collection", options.ApplicationsCollectionName); + } - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0261), exception.Message); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetAuthorizationsCollectionName_ThrowsAnExceptionForNullOrEmptyCollectionName(string name) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void SetAuthorizationsCollectionName_CollectionNameIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Act and assert + var exception = Assert.Throws(() => builder.SetAuthorizationsCollectionName(name)); - // Act - builder.SetAuthorizationsCollectionName("custom_collection"); + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0261), exception.Message); + } - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + [Fact] + public void SetAuthorizationsCollectionName_CollectionNameIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - Assert.Equal("custom_collection", options.AuthorizationsCollectionName); - } + // Act + builder.SetAuthorizationsCollectionName("custom_collection"); - [Theory] - [InlineData(null)] - [InlineData("")] - public void SetScopesCollectionName_ThrowsAnExceptionForNullOrEmptyCollectionName(string name) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - // Act and assert - var exception = Assert.Throws(() => builder.SetScopesCollectionName(name)); + Assert.Equal("custom_collection", options.AuthorizationsCollectionName); + } - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0261), exception.Message); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetScopesCollectionName_ThrowsAnExceptionForNullOrEmptyCollectionName(string name) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void SetScopesCollectionName_CollectionNameIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Act and assert + var exception = Assert.Throws(() => builder.SetScopesCollectionName(name)); - // Act - builder.SetScopesCollectionName("custom_collection"); + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0261), exception.Message); + } - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + [Fact] + public void SetScopesCollectionName_CollectionNameIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - Assert.Equal("custom_collection", options.ScopesCollectionName); - } + // Act + builder.SetScopesCollectionName("custom_collection"); - [Theory] - [InlineData(null)] - [InlineData("")] - public void SetTokensCollectionName_ThrowsAnExceptionForNullOrEmptyCollectionName(string name) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - // Act and assert - var exception = Assert.Throws(() => builder.SetTokensCollectionName(name)); + Assert.Equal("custom_collection", options.ScopesCollectionName); + } - Assert.Equal("name", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0261), exception.Message); - } + [Theory] + [InlineData(null)] + [InlineData("")] + public void SetTokensCollectionName_ThrowsAnExceptionForNullOrEmptyCollectionName(string name) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void SetTokensCollectionName_CollectionNameIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Act and assert + var exception = Assert.Throws(() => builder.SetTokensCollectionName(name)); - // Act - builder.SetTokensCollectionName("custom_collection"); + Assert.Equal("name", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0261), exception.Message); + } - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + [Fact] + public void SetTokensCollectionName_CollectionNameIsCorrectlySet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - Assert.Equal("custom_collection", options.TokensCollectionName); - } + // Act + builder.SetTokensCollectionName("custom_collection"); - [Fact] - public void UseDatabase_ThrowsAnExceptionForNullDatabase() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - // Act and assert - var exception = Assert.Throws(delegate - { - return builder.UseDatabase(database: null!); - }); + Assert.Equal("custom_collection", options.TokensCollectionName); + } - Assert.Equal("database", exception.ParamName); - } + [Fact] + public void UseDatabase_ThrowsAnExceptionForNullDatabase() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void UseDatabase_SetsDatabaseInOptions() + // Act and assert + var exception = Assert.Throws(delegate { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - var database = Mock.Of(); + return builder.UseDatabase(database: null!); + }); + + Assert.Equal("database", exception.ParamName); + } - // Act - builder.UseDatabase(database); + [Fact] + public void UseDatabase_SetsDatabaseInOptions() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + var database = Mock.Of(); - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; + // Act + builder.UseDatabase(database); - Assert.Equal(database, options.Database); - } + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; - private static OpenIddictMongoDbBuilder CreateBuilder(IServiceCollection services) - => services.AddOpenIddict().AddCore().UseMongoDb(); + Assert.Equal(database, options.Database); + } - private static IServiceCollection CreateServices() - { - var services = new ServiceCollection(); - services.AddOptions(); + private static OpenIddictMongoDbBuilder CreateBuilder(IServiceCollection services) + => services.AddOpenIddict().AddCore().UseMongoDb(); - return services; - } + private static IServiceCollection CreateServices() + { + var services = new ServiceCollection(); + services.AddOptions(); - public class CustomApplication : OpenIddictMongoDbApplication { } - public class CustomAuthorization : OpenIddictMongoDbAuthorization { } - public class CustomScope : OpenIddictMongoDbScope { } - public class CustomToken : OpenIddictMongoDbToken { } + return services; } + + public class CustomApplication : OpenIddictMongoDbApplication { } + public class CustomAuthorization : OpenIddictMongoDbAuthorization { } + public class CustomScope : OpenIddictMongoDbScope { } + public class CustomToken : OpenIddictMongoDbToken { } } diff --git a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs index f2b4fed8..fb06ce0e 100644 --- a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs +++ b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs @@ -14,99 +14,98 @@ using Moq; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb.Tests +namespace OpenIddict.MongoDb.Tests; + +public class OpenIddictMongoDbContextTests { - public class OpenIddictMongoDbContextTests + [Fact] + public async Task GetDatabaseAsync_ThrowsAnExceptionForCanceledToken() { - [Fact] - public async Task GetDatabaseAsync_ThrowsAnExceptionForCanceledToken() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - - var options = Mock.Of>(); - var token = new CancellationToken(canceled: true); + // Arrange + var services = new ServiceCollection(); + var provider = services.BuildServiceProvider(); - var context = new OpenIddictMongoDbContext(options, provider); - - // Act and assert - var exception = await Assert.ThrowsAsync(async delegate - { - await context.GetDatabaseAsync(token); - }); + var options = Mock.Of>(); + var token = new CancellationToken(canceled: true); - Assert.Equal(token, exception.CancellationToken); - } + var context = new OpenIddictMongoDbContext(options, provider); - [Fact] - public async Task GetDatabaseAsync_PrefersDatabaseRegisteredInOptionsToDatabaseRegisteredInDependencyInjectionContainer() + // Act and assert + var exception = await Assert.ThrowsAsync(async delegate { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of()); + await context.GetDatabaseAsync(token); + }); - var provider = services.BuildServiceProvider(); + Assert.Equal(token, exception.CancellationToken); + } - var database = Mock.Of(); - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictMongoDbOptions - { - Database = database - }); + [Fact] + public async Task GetDatabaseAsync_PrefersDatabaseRegisteredInOptionsToDatabaseRegisteredInDependencyInjectionContainer() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of()); - var context = new OpenIddictMongoDbContext(options, provider); + var provider = services.BuildServiceProvider(); - // Act and assert - Assert.Same(database, await context.GetDatabaseAsync(CancellationToken.None)); - } + var database = Mock.Of(); + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictMongoDbOptions + { + Database = database + }); - [Fact] - public async Task GetDatabaseAsync_ThrowsAnExceptionWhenDatabaseCannotBeFound() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); + var context = new OpenIddictMongoDbContext(options, provider); - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictMongoDbOptions - { - Database = null - }); + // Act and assert + Assert.Same(database, await context.GetDatabaseAsync(CancellationToken.None)); + } - var context = new OpenIddictMongoDbContext(options, provider); + [Fact] + public async Task GetDatabaseAsync_ThrowsAnExceptionWhenDatabaseCannotBeFound() + { + // Arrange + var services = new ServiceCollection(); + var provider = services.BuildServiceProvider(); - // Act and assert - var exception = await Assert.ThrowsAsync(async delegate + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictMongoDbOptions { - await context.GetDatabaseAsync(CancellationToken.None); + Database = null }); - Assert.Equal(SR.GetResourceString(SR.ID0262), exception.Message); - } + var context = new OpenIddictMongoDbContext(options, provider); - [Fact] - public async Task GetDatabaseAsync_UsesDatabaseRegisteredInDependencyInjectionContainer() + // Act and assert + var exception = await Assert.ThrowsAsync(async delegate { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of()); + await context.GetDatabaseAsync(CancellationToken.None); + }); - var database = Mock.Of(); - services.AddSingleton(database); + Assert.Equal(SR.GetResourceString(SR.ID0262), exception.Message); + } - var provider = services.BuildServiceProvider(); + [Fact] + public async Task GetDatabaseAsync_UsesDatabaseRegisteredInDependencyInjectionContainer() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of()); - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictMongoDbOptions - { - Database = null - }); + var database = Mock.Of(); + services.AddSingleton(database); + + var provider = services.BuildServiceProvider(); + + var options = Mock.Of>( + mock => mock.CurrentValue == new OpenIddictMongoDbOptions + { + Database = null + }); - var context = new OpenIddictMongoDbContext(options, provider); + var context = new OpenIddictMongoDbContext(options, provider); - // Act and assert - Assert.Same(database, await context.GetDatabaseAsync(CancellationToken.None)); - } + // Act and assert + Assert.Same(database, await context.GetDatabaseAsync(CancellationToken.None)); } } diff --git a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbExtensionsTests.cs b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbExtensionsTests.cs index 8d2d5aa8..56f5a9a6 100644 --- a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbExtensionsTests.cs +++ b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbExtensionsTests.cs @@ -12,106 +12,105 @@ using OpenIddict.Core; using OpenIddict.MongoDb.Models; using Xunit; -namespace OpenIddict.MongoDb.Tests +namespace OpenIddict.MongoDb.Tests; + +public class OpenIddictMongoDbExtensionsTests { - public class OpenIddictMongoDbExtensionsTests + [Fact] + public void UseMongoDb_ThrowsAnExceptionForNullBuilder() + { + // Arrange + var builder = (OpenIddictCoreBuilder) null!; + + // Act and assert + var exception = Assert.Throws(() => builder.UseMongoDb()); + + Assert.Equal("builder", exception.ParamName); + } + + [Fact] + public void UseMongoDb_ThrowsAnExceptionForNullConfiguration() { - [Fact] - public void UseMongoDb_ThrowsAnExceptionForNullBuilder() - { - // Arrange - var builder = (OpenIddictCoreBuilder) null!; - - // Act and assert - var exception = Assert.Throws(() => builder.UseMongoDb()); - - Assert.Equal("builder", exception.ParamName); - } - - [Fact] - public void UseMongoDb_ThrowsAnExceptionForNullConfiguration() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.UseMongoDb(configuration: null!)); - - Assert.Equal("configuration", exception.ParamName); - } - - [Fact] - public void UseMongoDb_RegistersDefaultEntities() - { - // Arrange - var services = new ServiceCollection().AddOptions(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseMongoDb(); - - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; - - Assert.Equal(typeof(OpenIddictMongoDbApplication), options.DefaultApplicationType); - Assert.Equal(typeof(OpenIddictMongoDbAuthorization), options.DefaultAuthorizationType); - Assert.Equal(typeof(OpenIddictMongoDbScope), options.DefaultScopeType); - Assert.Equal(typeof(OpenIddictMongoDbToken), options.DefaultTokenType); - } - - [Theory] - [InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictMongoDbApplicationStoreResolver))] - [InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictMongoDbAuthorizationStoreResolver))] - [InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictMongoDbScopeStoreResolver))] - [InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictMongoDbTokenStoreResolver))] - public void UseMongoDb_RegistersMongoDbStoreResolvers(Type serviceType, Type implementationType) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseMongoDb(); - - // Assert - Assert.Contains(services, service => service.ServiceType == serviceType && - service.ImplementationType == implementationType); - } - - [Theory] - [InlineData(typeof(OpenIddictMongoDbApplicationStore<>))] - [InlineData(typeof(OpenIddictMongoDbAuthorizationStore<>))] - [InlineData(typeof(OpenIddictMongoDbScopeStore<>))] - [InlineData(typeof(OpenIddictMongoDbTokenStore<>))] - public void UseMongoDb_RegistersMongoDbStore(Type type) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseMongoDb(); - - // Assert - Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type); - } - - [Fact] - public void UseMongoDb_RegistersMongoDbContext() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); - - // Act - builder.UseMongoDb(); - - // Assert - Assert.Contains(services, service => service.Lifetime == ServiceLifetime.Singleton && - service.ServiceType == typeof(IOpenIddictMongoDbContext) && - service.ImplementationType == typeof(OpenIddictMongoDbContext)); - } + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.UseMongoDb(configuration: null!)); + + Assert.Equal("configuration", exception.ParamName); + } + + [Fact] + public void UseMongoDb_RegistersDefaultEntities() + { + // Arrange + var services = new ServiceCollection().AddOptions(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseMongoDb(); + + // Assert + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().CurrentValue; + + Assert.Equal(typeof(OpenIddictMongoDbApplication), options.DefaultApplicationType); + Assert.Equal(typeof(OpenIddictMongoDbAuthorization), options.DefaultAuthorizationType); + Assert.Equal(typeof(OpenIddictMongoDbScope), options.DefaultScopeType); + Assert.Equal(typeof(OpenIddictMongoDbToken), options.DefaultTokenType); + } + + [Theory] + [InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictMongoDbApplicationStoreResolver))] + [InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictMongoDbAuthorizationStoreResolver))] + [InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictMongoDbScopeStoreResolver))] + [InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictMongoDbTokenStoreResolver))] + public void UseMongoDb_RegistersMongoDbStoreResolvers(Type serviceType, Type implementationType) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseMongoDb(); + + // Assert + Assert.Contains(services, service => service.ServiceType == serviceType && + service.ImplementationType == implementationType); + } + + [Theory] + [InlineData(typeof(OpenIddictMongoDbApplicationStore<>))] + [InlineData(typeof(OpenIddictMongoDbAuthorizationStore<>))] + [InlineData(typeof(OpenIddictMongoDbScopeStore<>))] + [InlineData(typeof(OpenIddictMongoDbTokenStore<>))] + public void UseMongoDb_RegistersMongoDbStore(Type type) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseMongoDb(); + + // Assert + Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type); + } + + [Fact] + public void UseMongoDb_RegistersMongoDbContext() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); + + // Act + builder.UseMongoDb(); + + // Assert + Assert.Contains(services, service => service.Lifetime == ServiceLifetime.Singleton && + service.ServiceType == typeof(IOpenIddictMongoDbContext) && + service.ImplementationType == typeof(OpenIddictMongoDbContext)); } } diff --git a/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbApplicationStoreResolverTests.cs b/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbApplicationStoreResolverTests.cs index d7b8fed1..26c5a076 100644 --- a/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbApplicationStoreResolverTests.cs +++ b/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbApplicationStoreResolverTests.cs @@ -13,61 +13,60 @@ using OpenIddict.MongoDb.Models; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb.Tests +namespace OpenIddict.MongoDb.Tests; + +public class OpenIddictMongoDbApplicationStoreResolverTests { - public class OpenIddictMongoDbApplicationStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbApplicationStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbApplicationStoreResolver(provider); - // Act and assert - Assert.NotNull(resolver.Get()); - } + // Act and assert + Assert.NotNull(resolver.Get()); + } - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbApplicationStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbApplicationStoreResolver(provider); - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); - Assert.Equal(SR.GetResourceString(SR.ID0257), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0257), exception.Message); + } - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbApplicationStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbApplicationStoreResolver(provider); - // Act and assert - Assert.NotNull(resolver.Get()); - } + // Act and assert + Assert.NotNull(resolver.Get()); + } - private static OpenIddictMongoDbApplicationStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of>()).Object; + private static OpenIddictMongoDbApplicationStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of>()).Object; - public class CustomApplication { } + public class CustomApplication { } - public class MyApplication : OpenIddictMongoDbApplication { } - } + public class MyApplication : OpenIddictMongoDbApplication { } } diff --git a/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbAuthorizationStoreResolverTests.cs b/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbAuthorizationStoreResolverTests.cs index 67a328f0..b405ca65 100644 --- a/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbAuthorizationStoreResolverTests.cs +++ b/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbAuthorizationStoreResolverTests.cs @@ -13,61 +13,60 @@ using OpenIddict.MongoDb.Models; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb.Tests +namespace OpenIddict.MongoDb.Tests; + +public class OpenIddictMongoDbAuthorizationStoreResolverTests { - public class OpenIddictMongoDbAuthorizationStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbAuthorizationStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbAuthorizationStoreResolver(provider); - // Act and assert - Assert.NotNull(resolver.Get()); - } + // Act and assert + Assert.NotNull(resolver.Get()); + } - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbAuthorizationStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbAuthorizationStoreResolver(provider); - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); - Assert.Equal(SR.GetResourceString(SR.ID0258), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0258), exception.Message); + } - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbAuthorizationStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbAuthorizationStoreResolver(provider); - // Act and assert - Assert.NotNull(resolver.Get()); - } + // Act and assert + Assert.NotNull(resolver.Get()); + } - private static OpenIddictMongoDbAuthorizationStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of>()).Object; + private static OpenIddictMongoDbAuthorizationStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of>()).Object; - public class CustomAuthorization { } + public class CustomAuthorization { } - public class MyAuthorization : OpenIddictMongoDbAuthorization { } - } + public class MyAuthorization : OpenIddictMongoDbAuthorization { } } diff --git a/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbScopeStoreResolverTests.cs b/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbScopeStoreResolverTests.cs index 2ade7ccc..d936ef37 100644 --- a/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbScopeStoreResolverTests.cs +++ b/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbScopeStoreResolverTests.cs @@ -13,61 +13,60 @@ using OpenIddict.MongoDb.Models; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb.Tests +namespace OpenIddict.MongoDb.Tests; + +public class OpenIddictMongoDbScopeStoreResolverTests { - public class OpenIddictMongoDbScopeStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbScopeStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbScopeStoreResolver(provider); - // Act and assert - Assert.NotNull(resolver.Get()); - } + // Act and assert + Assert.NotNull(resolver.Get()); + } - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbScopeStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbScopeStoreResolver(provider); - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); - Assert.Equal(SR.GetResourceString(SR.ID0259), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0259), exception.Message); + } - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbScopeStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbScopeStoreResolver(provider); - // Act and assert - Assert.NotNull(resolver.Get()); - } + // Act and assert + Assert.NotNull(resolver.Get()); + } - private static OpenIddictMongoDbScopeStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of>()).Object; + private static OpenIddictMongoDbScopeStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of>()).Object; - public class CustomScope { } + public class CustomScope { } - public class MyScope : OpenIddictMongoDbScope { } - } + public class MyScope : OpenIddictMongoDbScope { } } diff --git a/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbTokenStoreResolverTests.cs b/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbTokenStoreResolverTests.cs index 926c350d..ca6f4e0e 100644 --- a/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbTokenStoreResolverTests.cs +++ b/test/OpenIddict.MongoDb.Tests/Resolvers/OpenIddictMongoDbTokenStoreResolverTests.cs @@ -13,61 +13,60 @@ using OpenIddict.MongoDb.Models; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.MongoDb.Tests +namespace OpenIddict.MongoDb.Tests; + +public class OpenIddictMongoDbTokenStoreResolverTests { - public class OpenIddictMongoDbTokenStoreResolverTests + [Fact] + public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() { - [Fact] - public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbTokenStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbTokenStoreResolver(provider); - // Act and assert - Assert.NotNull(resolver.Get()); - } + // Act and assert + Assert.NotNull(resolver.Get()); + } - [Fact] - public void Get_ThrowsAnExceptionForInvalidEntityType() - { - // Arrange - var services = new ServiceCollection(); + [Fact] + public void Get_ThrowsAnExceptionForInvalidEntityType() + { + // Arrange + var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbTokenStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbTokenStoreResolver(provider); - // Act and assert - var exception = Assert.Throws(() => resolver.Get()); + // Act and assert + var exception = Assert.Throws(() => resolver.Get()); - Assert.Equal(SR.GetResourceString(SR.ID0260), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0260), exception.Message); + } - [Fact] - public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() - { - // Arrange - var services = new ServiceCollection(); - services.AddSingleton(Mock.Of>()); - services.AddSingleton(CreateStore()); + [Fact] + public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of>()); + services.AddSingleton(CreateStore()); - var provider = services.BuildServiceProvider(); - var resolver = new OpenIddictMongoDbTokenStoreResolver(provider); + var provider = services.BuildServiceProvider(); + var resolver = new OpenIddictMongoDbTokenStoreResolver(provider); - // Act and assert - Assert.NotNull(resolver.Get()); - } + // Act and assert + Assert.NotNull(resolver.Get()); + } - private static OpenIddictMongoDbTokenStore CreateStore() - => new Mock>( - Mock.Of(), - Mock.Of>()).Object; + private static OpenIddictMongoDbTokenStore CreateStore() + => new Mock>( + Mock.Of(), + Mock.Of>()).Object; - public class CustomToken { } + public class CustomToken { } - public class MyToken : OpenIddictMongoDbToken { } - } + public class MyToken : OpenIddictMongoDbToken { } } diff --git a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzBuilderTests.cs b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzBuilderTests.cs index 88bf0995..2204e28c 100644 --- a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzBuilderTests.cs +++ b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzBuilderTests.cs @@ -4,184 +4,183 @@ using Microsoft.Extensions.Options; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Quartz.Tests +namespace OpenIddict.Quartz.Tests; + +public class OpenIddictQuartzBuilderTests { - public class OpenIddictQuartzBuilderTests + [Fact] + public void Constructor_ThrowsAnExceptionForNullServices() + { + // Arrange + var services = (IServiceCollection) null!; + + // Act and assert + var exception = Assert.Throws(() => new OpenIddictQuartzBuilder(services)); + + Assert.Equal("services", exception.ParamName); + } + + [Fact] + public void Configure_DelegateIsCorrectlyRegistered() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + var configuration = new Action(options => { }); + + // Act + builder.Configure(configuration); + + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(IConfigureOptions) && + service.ImplementationInstance is ConfigureNamedOptions options && + options.Action == configuration && string.IsNullOrEmpty(options.Name)); + } + + [Fact] + public void Configure_ThrowsAnExceptionWhenConfigurationIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.Configure(configuration: null!)); + Assert.Equal("configuration", exception.ParamName); + } + + [Fact] + public void DisableAuthorizationPruning_AuthorizationPruningIsDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.DisableAuthorizationPruning(); + + var options = GetOptions(services); + + // Assert + Assert.True(options.DisableAuthorizationPruning); + } + + [Fact] + public void DisableTokenPruning_TokenPruningIsDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.DisableTokenPruning(); + + var options = GetOptions(services); + + // Assert + Assert.True(options.DisableTokenPruning); + } + + [Fact] + public void SetMaximumRefireCount_ThrowsAnExceptionForNegativeCount() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetMaximumRefireCount(-1)); + + Assert.Equal("count", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0279), exception.Message); + } + + [Fact] + public void SetMaximumRefireCount_MaximumRefireCountIsSet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.SetMaximumRefireCount(42); + + var options = GetOptions(services); + + // Assert + Assert.Equal(42, options.MaximumRefireCount); + } + + [Fact] + public void SetMinimumAuthorizationLifespan_ThrowsAnExceptionForNegativeLifespan() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetMinimumAuthorizationLifespan(TimeSpan.FromSeconds(-1))); + + Assert.Equal("lifespan", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0280), exception.Message); + } + + [Fact] + public void SetMinimumAuthorizationLifespan_MinimumAuthorizationLifespanIsSet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.SetMinimumAuthorizationLifespan(TimeSpan.FromDays(42)); + + var options = GetOptions(services); + + // Assert + Assert.Equal(42, options.MinimumAuthorizationLifespan.TotalDays); + } + + [Fact] + public void SetMinimumTokenLifespan_ThrowsAnExceptionForNegativeLifespan() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetMinimumTokenLifespan(TimeSpan.FromSeconds(-1))); + + Assert.Equal("lifespan", exception.ParamName); + Assert.StartsWith(SR.GetResourceString(SR.ID0280), exception.Message); + } + + [Fact] + public void SetMinimumTokenLifespan_MinimumTokenLifespanIsSet() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.SetMinimumTokenLifespan(TimeSpan.FromDays(42)); + + var options = GetOptions(services); + + // Assert + Assert.Equal(42, options.MinimumTokenLifespan.TotalDays); + } + + private static IServiceCollection CreateServices() + => new ServiceCollection().AddOptions(); + + private static OpenIddictQuartzBuilder CreateBuilder(IServiceCollection services) + => new OpenIddictQuartzBuilder(services); + + private static OpenIddictQuartzOptions GetOptions(IServiceCollection services) { - [Fact] - public void Constructor_ThrowsAnExceptionForNullServices() - { - // Arrange - var services = (IServiceCollection) null!; - - // Act and assert - var exception = Assert.Throws(() => new OpenIddictQuartzBuilder(services)); - - Assert.Equal("services", exception.ParamName); - } - - [Fact] - public void Configure_DelegateIsCorrectlyRegistered() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - var configuration = new Action(options => { }); - - // Act - builder.Configure(configuration); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(IConfigureOptions) && - service.ImplementationInstance is ConfigureNamedOptions options && - options.Action == configuration && string.IsNullOrEmpty(options.Name)); - } - - [Fact] - public void Configure_ThrowsAnExceptionWhenConfigurationIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.Configure(configuration: null!)); - Assert.Equal("configuration", exception.ParamName); - } - - [Fact] - public void DisableAuthorizationPruning_AuthorizationPruningIsDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.DisableAuthorizationPruning(); - - var options = GetOptions(services); - - // Assert - Assert.True(options.DisableAuthorizationPruning); - } - - [Fact] - public void DisableTokenPruning_TokenPruningIsDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.DisableTokenPruning(); - - var options = GetOptions(services); - - // Assert - Assert.True(options.DisableTokenPruning); - } - - [Fact] - public void SetMaximumRefireCount_ThrowsAnExceptionForNegativeCount() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetMaximumRefireCount(-1)); - - Assert.Equal("count", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0279), exception.Message); - } - - [Fact] - public void SetMaximumRefireCount_MaximumRefireCountIsSet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.SetMaximumRefireCount(42); - - var options = GetOptions(services); - - // Assert - Assert.Equal(42, options.MaximumRefireCount); - } - - [Fact] - public void SetMinimumAuthorizationLifespan_ThrowsAnExceptionForNegativeLifespan() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetMinimumAuthorizationLifespan(TimeSpan.FromSeconds(-1))); - - Assert.Equal("lifespan", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0280), exception.Message); - } - - [Fact] - public void SetMinimumAuthorizationLifespan_MinimumAuthorizationLifespanIsSet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.SetMinimumAuthorizationLifespan(TimeSpan.FromDays(42)); - - var options = GetOptions(services); - - // Assert - Assert.Equal(42, options.MinimumAuthorizationLifespan.TotalDays); - } - - [Fact] - public void SetMinimumTokenLifespan_ThrowsAnExceptionForNegativeLifespan() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetMinimumTokenLifespan(TimeSpan.FromSeconds(-1))); - - Assert.Equal("lifespan", exception.ParamName); - Assert.StartsWith(SR.GetResourceString(SR.ID0280), exception.Message); - } - - [Fact] - public void SetMinimumTokenLifespan_MinimumTokenLifespanIsSet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.SetMinimumTokenLifespan(TimeSpan.FromDays(42)); - - var options = GetOptions(services); - - // Assert - Assert.Equal(42, options.MinimumTokenLifespan.TotalDays); - } - - private static IServiceCollection CreateServices() - => new ServiceCollection().AddOptions(); - - private static OpenIddictQuartzBuilder CreateBuilder(IServiceCollection services) - => new OpenIddictQuartzBuilder(services); - - private static OpenIddictQuartzOptions GetOptions(IServiceCollection services) - { - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - return options.Value; - } + var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>(); + return options.Value; } } diff --git a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs index 5af7f856..95d9906a 100644 --- a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs +++ b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzConfigurationTests.cs @@ -1,36 +1,35 @@ using Quartz; using Xunit; -namespace OpenIddict.Quartz.Tests +namespace OpenIddict.Quartz.Tests; + +public class OpenIddictQuartzConfigurationTests { - public class OpenIddictQuartzConfigurationTests + [Fact] + public void UseQuartz_RegistersJobDetails() { - [Fact] - public void UseQuartz_RegistersJobDetails() - { - // Arrange - var options = new QuartzOptions(); - var configuration = new OpenIddictQuartzConfiguration(); + // Arrange + var options = new QuartzOptions(); + var configuration = new OpenIddictQuartzConfiguration(); - // Act - configuration.Configure(options); + // Act + configuration.Configure(options); - // Assert - Assert.Contains(options.JobDetails, job => job.Key.Equals(OpenIddictQuartzJob.Identity)); - } + // Assert + Assert.Contains(options.JobDetails, job => job.Key.Equals(OpenIddictQuartzJob.Identity)); + } - [Fact] - public void UseQuartz_RegistersTriggerDetails() - { - // Arrange - var options = new QuartzOptions(); - var configuration = new OpenIddictQuartzConfiguration(); + [Fact] + public void UseQuartz_RegistersTriggerDetails() + { + // Arrange + var options = new QuartzOptions(); + var configuration = new OpenIddictQuartzConfiguration(); - // Act - configuration.Configure(options); + // Act + configuration.Configure(options); - // Assert - Assert.Contains(options.Triggers, trigger => trigger.JobKey.Equals(OpenIddictQuartzJob.Identity)); - } + // Assert + Assert.Contains(options.Triggers, trigger => trigger.JobKey.Equals(OpenIddictQuartzJob.Identity)); } } diff --git a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs index 127e27d2..0dd933d2 100644 --- a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs +++ b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzExtensionsTests.cs @@ -4,71 +4,70 @@ using Microsoft.Extensions.Options; using Quartz; using Xunit; -namespace OpenIddict.Quartz.Tests +namespace OpenIddict.Quartz.Tests; + +public class OpenIddictQuartzExtensionsTests { - public class OpenIddictQuartzExtensionsTests + [Fact] + public void UseQuartz_ThrowsAnExceptionForNullBuilder() { - [Fact] - public void UseQuartz_ThrowsAnExceptionForNullBuilder() - { - // Arrange - var builder = (OpenIddictCoreBuilder) null!; + // Arrange + var builder = (OpenIddictCoreBuilder) null!; - // Act and assert - var exception = Assert.Throws(() => builder.UseQuartz()); + // Act and assert + var exception = Assert.Throws(() => builder.UseQuartz()); - Assert.Equal("builder", exception.ParamName); - } + Assert.Equal("builder", exception.ParamName); + } - [Fact] - public void UseQuartz_ThrowsAnExceptionForNullConfiguration() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); + [Fact] + public void UseQuartz_ThrowsAnExceptionForNullConfiguration() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.UseQuartz(configuration: null!)); + // Act and assert + var exception = Assert.Throws(() => builder.UseQuartz(configuration: null!)); - Assert.Equal("configuration", exception.ParamName); - } + Assert.Equal("configuration", exception.ParamName); + } - [Fact] - public void UseQuartz_RegistersJobService() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); + [Fact] + public void UseQuartz_RegistersJobService() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); - // Act - builder.UseQuartz(); + // Act + builder.UseQuartz(); - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(OpenIddictQuartzJob) && - service.ImplementationType == typeof(OpenIddictQuartzJob) && - service.Lifetime == ServiceLifetime.Transient); - } + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(OpenIddictQuartzJob) && + service.ImplementationType == typeof(OpenIddictQuartzJob) && + service.Lifetime == ServiceLifetime.Transient); + } - [Fact] - public void UseQuartz_CanBeSafelyInvokedMultipleTimes() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictCoreBuilder(services); + [Fact] + public void UseQuartz_CanBeSafelyInvokedMultipleTimes() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictCoreBuilder(services); - // Act - builder.UseQuartz(); - builder.UseQuartz(); - builder.UseQuartz(); + // Act + builder.UseQuartz(); + builder.UseQuartz(); + builder.UseQuartz(); - // Assert - Assert.Single(services, service => service.ServiceType == typeof(OpenIddictQuartzJob) && - service.ImplementationType == typeof(OpenIddictQuartzJob) && - service.Lifetime == ServiceLifetime.Transient); + // Assert + Assert.Single(services, service => service.ServiceType == typeof(OpenIddictQuartzJob) && + service.ImplementationType == typeof(OpenIddictQuartzJob) && + service.Lifetime == ServiceLifetime.Transient); - Assert.Single(services, service => service.ServiceType == typeof(IConfigureOptions) && - service.ImplementationType == typeof(OpenIddictQuartzConfiguration) && - service.Lifetime == ServiceLifetime.Singleton); - } + Assert.Single(services, service => service.ServiceType == typeof(IConfigureOptions) && + service.ImplementationType == typeof(OpenIddictQuartzConfiguration) && + service.Lifetime == ServiceLifetime.Singleton); } } diff --git a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs index 786f5646..1e6b46c2 100644 --- a/test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs +++ b/test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs @@ -9,341 +9,340 @@ using Quartz; using Xunit; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Quartz.Tests +namespace OpenIddict.Quartz.Tests; + +public class OpenIddictQuartzJobTests { - public class OpenIddictQuartzJobTests + [Fact] + public void Constructor_ThrowsAnException() { - [Fact] - public void Constructor_ThrowsAnException() - { - // Arrange, act and assert - var exception = Assert.Throws(() => new OpenIddictQuartzJob()); + // Arrange, act and assert + var exception = Assert.Throws(() => new OpenIddictQuartzJob()); - Assert.Equal(SR.GetResourceString(SR.ID0082), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0082), exception.Message); + } - [Fact] - public async Task Execute_UsesServiceScope() - { - // Arrange - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); + [Fact] + public async Task Execute_UsesServiceScope() + { + // Arrange + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); - var scope = Mock.Of(scope => scope.ServiceProvider == provider); - var factory = Mock.Of(factory => factory.CreateScope() == scope); - var monitor = Mock.Of>( - monitor => monitor.CurrentValue == new OpenIddictQuartzOptions()); + var scope = Mock.Of(scope => scope.ServiceProvider == provider); + var factory = Mock.Of(factory => factory.CreateScope() == scope); + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == new OpenIddictQuartzOptions()); - var job = new OpenIddictQuartzJob(monitor, - Mock.Of(provider => provider.GetService(typeof(IServiceScopeFactory)) == factory)); + var job = new OpenIddictQuartzJob(monitor, + Mock.Of(provider => provider.GetService(typeof(IServiceScopeFactory)) == factory)); - // Act - await job.Execute(Mock.Of()); + // Act + await job.Execute(Mock.Of()); - Mock.Get(factory).Verify(factory => factory.CreateScope(), Times.Once()); - Mock.Get(scope).Verify(scope => scope.Dispose(), Times.Once()); - } + Mock.Get(factory).Verify(factory => factory.CreateScope(), Times.Once()); + Mock.Get(scope).Verify(scope => scope.Dispose(), Times.Once()); + } - [Fact] - public async Task Execute_IgnoresPruningWhenTokenPruningIsDisabled() + [Fact] + public async Task Execute_IgnoresPruningWhenTokenPruningIsDisabled() + { + // Arrange + + var manager = new Mock(); + + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && + provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); + + var job = CreateJob(provider, new OpenIddictQuartzOptions { - // Arrange + DisableTokenPruning = true + }); - var manager = new Mock(); + // Act + await job.Execute(Mock.Of()); - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); + // Assert + manager.Verify(manager => manager.PruneAsync(It.IsAny(), It.IsAny()), Times.Never()); + } - var job = CreateJob(provider, new OpenIddictQuartzOptions - { - DisableTokenPruning = true - }); + [Fact] + public async Task Execute_IgnoresPruningWhenAuthorizationPruningIsDisabled() + { + // Arrange - // Act - await job.Execute(Mock.Of()); + var manager = new Mock(); - // Assert - manager.Verify(manager => manager.PruneAsync(It.IsAny(), It.IsAny()), Times.Never()); - } + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); - [Fact] - public async Task Execute_IgnoresPruningWhenAuthorizationPruningIsDisabled() + var job = CreateJob(provider, new OpenIddictQuartzOptions { - // Arrange + DisableAuthorizationPruning = true + }); - var manager = new Mock(); + // Act + await job.Execute(Mock.Of()); - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); + // Assert + manager.Verify(manager => manager.PruneAsync(It.IsAny(), It.IsAny()), Times.Never()); + } - var job = CreateJob(provider, new OpenIddictQuartzOptions - { - DisableAuthorizationPruning = true - }); + [Fact] + public async Task Execute_UnschedulesTriggersWhenTokenManagerIsMissing() + { + // Arrange + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && + provider.GetService(typeof(IOpenIddictTokenManager)) == null); - // Act - await job.Execute(Mock.Of()); + var job = CreateJob(provider); - // Assert - manager.Verify(manager => manager.PruneAsync(It.IsAny(), It.IsAny()), Times.Never()); - } + // Act and assert + var exception = await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); - [Fact] - public async Task Execute_UnschedulesTriggersWhenTokenManagerIsMissing() - { - // Arrange - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == null); + Assert.False(exception.RefireImmediately); + Assert.True(exception.UnscheduleAllTriggers); + Assert.True(exception.UnscheduleFiringTrigger); - var job = CreateJob(provider); + Assert.IsType(exception.InnerException); + Assert.Equal(SR.GetResourceString(SR.ID0278), exception.InnerException!.Message); + } - // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); + [Fact] + public async Task Execute_UnschedulesTriggersWhenAuthorizationManagerIsMissing() + { + // Arrange + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == null); - Assert.False(exception.RefireImmediately); - Assert.True(exception.UnscheduleAllTriggers); - Assert.True(exception.UnscheduleFiringTrigger); + var job = CreateJob(provider); - Assert.IsType(exception.InnerException); - Assert.Equal(SR.GetResourceString(SR.ID0278), exception.InnerException!.Message); - } + // Act and assert + var exception = await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); - [Fact] - public async Task Execute_UnschedulesTriggersWhenAuthorizationManagerIsMissing() - { - // Arrange - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == null); + Assert.False(exception.RefireImmediately); + Assert.True(exception.UnscheduleAllTriggers); + Assert.True(exception.UnscheduleFiringTrigger); - var job = CreateJob(provider); + Assert.IsType(exception.InnerException); + Assert.Equal(SR.GetResourceString(SR.ID0278), exception.InnerException!.Message); + } - // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); + [Fact] + public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringTokenPruning() + { + // Arrange + var manager = new Mock(); + manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) + .Throws(new OutOfMemoryException()); - Assert.False(exception.RefireImmediately); - Assert.True(exception.UnscheduleAllTriggers); - Assert.True(exception.UnscheduleFiringTrigger); + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && + provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); - Assert.IsType(exception.InnerException); - Assert.Equal(SR.GetResourceString(SR.ID0278), exception.InnerException!.Message); - } + var job = CreateJob(provider); - [Fact] - public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringTokenPruning() - { - // Arrange - var manager = new Mock(); - manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) - .Throws(new OutOfMemoryException()); + // Act and assert + await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); + } - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); + [Fact] + public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringAuthorizationPruning() + { + // Arrange + var manager = new Mock(); + manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) + .Throws(new OutOfMemoryException()); - var job = CreateJob(provider); + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); - // Act and assert - await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); - } + var job = CreateJob(provider); - [Fact] - public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringAuthorizationPruning() - { - // Arrange - var manager = new Mock(); - manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) - .Throws(new OutOfMemoryException()); + // Act and assert + await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); + } - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); + [Fact] + public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringTokenPruning() + { + // Arrange + var token = new CancellationToken(canceled: true); - var job = CreateJob(provider); + var manager = new Mock(); + manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) + .Throws(new OperationCanceledException(token)); - // Act and assert - await Assert.ThrowsAsync(() => job.Execute(Mock.Of())); - } + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && + provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); - [Fact] - public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringTokenPruning() - { - // Arrange - var token = new CancellationToken(canceled: true); + var context = Mock.Of(context => context.CancellationToken == token); - var manager = new Mock(); - manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) - .Throws(new OperationCanceledException(token)); + var job = CreateJob(provider); - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of() && - provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object); + // Act and assert + var exception = await Assert.ThrowsAsync(() => job.Execute(context)); - var context = Mock.Of(context => context.CancellationToken == token); + Assert.False(exception.RefireImmediately); - var job = CreateJob(provider); + manager.Verify(manager => manager.PruneAsync(It.IsAny(), It.IsAny()), Times.Once()); + } - // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + [Fact] + public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringAuthorizationPruning() + { + // Arrange + var token = new CancellationToken(canceled: true); - Assert.False(exception.RefireImmediately); + var manager = new Mock(); + manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) + .Throws(new OperationCanceledException(token)); - manager.Verify(manager => manager.PruneAsync(It.IsAny(), It.IsAny()), Times.Once()); - } + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); - [Fact] - public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringAuthorizationPruning() - { - // Arrange - var token = new CancellationToken(canceled: true); + var context = Mock.Of(context => context.CancellationToken == token); + + var job = CreateJob(provider); + + // Act and assert + var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + + Assert.False(exception.RefireImmediately); + + manager.Verify(manager => manager.PruneAsync(It.IsAny(), It.IsAny()), Times.Once()); + } + + [Fact] + public async Task Execute_AllowsRefiringWhenExceptionsAreThrown() + { + // Arrange + var provider = new Mock(); + provider.Setup(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager))) + .Returns(CreateAuthorizationManager(new ApplicationException())); - var manager = new Mock(); - manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) - .Throws(new OperationCanceledException(token)); + provider.Setup(provider => provider.GetService(typeof(IOpenIddictTokenManager))) + .Returns(CreateTokenManager(new ApplicationException())); - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); + var context = Mock.Of(context => context.RefireCount == 0); - var context = Mock.Of(context => context.CancellationToken == token); + var job = CreateJob(provider.Object); - var job = CreateJob(provider); + // Act and assert + var exception = await Assert.ThrowsAsync(() => job.Execute(context)); - // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + Assert.True(exception.RefireImmediately); + Assert.IsType(exception.InnerException); + Assert.Equal(2, ((AggregateException) exception.InnerException!).InnerExceptions.Count); + Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[0]); + Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[1]); - Assert.False(exception.RefireImmediately); + static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception) + { + var mock = new Mock(); + mock.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) + .Throws(exception); - manager.Verify(manager => manager.PruneAsync(It.IsAny(), It.IsAny()), Times.Once()); + return mock.Object; } - [Fact] - public async Task Execute_AllowsRefiringWhenExceptionsAreThrown() + static IOpenIddictTokenManager CreateTokenManager(Exception exception) { - // Arrange - var provider = new Mock(); - provider.Setup(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager))) - .Returns(CreateAuthorizationManager(new ApplicationException())); + var mock = new Mock(); + mock.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) + .Throws(exception); - provider.Setup(provider => provider.GetService(typeof(IOpenIddictTokenManager))) - .Returns(CreateTokenManager(new ApplicationException())); + return mock.Object; + } + } - var context = Mock.Of(context => context.RefireCount == 0); + [Fact] + public async Task Execute_AllowsRefiringWhenAggregateExceptionsAreThrown() + { + // Arrange + var provider = new Mock(); + provider.Setup(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager))) + .Returns(CreateAuthorizationManager(new AggregateException( + new InvalidOperationException(), new ApplicationException()))); - var job = CreateJob(provider.Object); + provider.Setup(provider => provider.GetService(typeof(IOpenIddictTokenManager))) + .Returns(CreateTokenManager(new AggregateException( + new InvalidOperationException(), new ApplicationException()))); - // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + var context = Mock.Of(context => context.RefireCount == 0); - Assert.True(exception.RefireImmediately); - Assert.IsType(exception.InnerException); - Assert.Equal(2, ((AggregateException) exception.InnerException!).InnerExceptions.Count); - Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[0]); - Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[1]); + var job = CreateJob(provider.Object); - static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception) - { - var mock = new Mock(); - mock.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) - .Throws(exception); + // Act and assert + var exception = await Assert.ThrowsAsync(() => job.Execute(context)); - return mock.Object; - } + Assert.True(exception.RefireImmediately); + Assert.IsType(exception.InnerException); + Assert.Equal(4, ((AggregateException) exception.InnerException!).InnerExceptions.Count); + Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[0]); + Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[1]); + Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[2]); + Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[3]); - static IOpenIddictTokenManager CreateTokenManager(Exception exception) - { - var mock = new Mock(); - mock.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) - .Throws(exception); + static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception) + { + var mock = new Mock(); + mock.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) + .Throws(exception); - return mock.Object; - } + return mock.Object; } - [Fact] - public async Task Execute_AllowsRefiringWhenAggregateExceptionsAreThrown() + static IOpenIddictTokenManager CreateTokenManager(Exception exception) { - // Arrange - var provider = new Mock(); - provider.Setup(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager))) - .Returns(CreateAuthorizationManager(new AggregateException( - new InvalidOperationException(), new ApplicationException()))); - - provider.Setup(provider => provider.GetService(typeof(IOpenIddictTokenManager))) - .Returns(CreateTokenManager(new AggregateException( - new InvalidOperationException(), new ApplicationException()))); - - var context = Mock.Of(context => context.RefireCount == 0); - - var job = CreateJob(provider.Object); - - // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); - - Assert.True(exception.RefireImmediately); - Assert.IsType(exception.InnerException); - Assert.Equal(4, ((AggregateException) exception.InnerException!).InnerExceptions.Count); - Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[0]); - Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[1]); - Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[2]); - Assert.IsType(((AggregateException) exception.InnerException!).InnerExceptions[3]); - - static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception) - { - var mock = new Mock(); - mock.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) - .Throws(exception); - - return mock.Object; - } - - static IOpenIddictTokenManager CreateTokenManager(Exception exception) - { - var mock = new Mock(); - mock.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) - .Throws(exception); - - return mock.Object; - } + var mock = new Mock(); + mock.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) + .Throws(exception); + + return mock.Object; } + } - [Fact] - public async Task Execute_DisallowsRefiringWhenMaximumRefireCountIsReached() - { - // Arrange - var manager = new Mock(); - manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) - .Throws(new ApplicationException()); + [Fact] + public async Task Execute_DisallowsRefiringWhenMaximumRefireCountIsReached() + { + // Arrange + var manager = new Mock(); + manager.Setup(manager => manager.PruneAsync(It.IsAny(), It.IsAny())) + .Throws(new ApplicationException()); - var provider = Mock.Of(provider => - provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && - provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); + var provider = Mock.Of(provider => + provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object && + provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of()); - var context = Mock.Of(context => context.RefireCount == 5); + var context = Mock.Of(context => context.RefireCount == 5); - var job = CreateJob(provider, new OpenIddictQuartzOptions - { - MaximumRefireCount = 5 - }); + var job = CreateJob(provider, new OpenIddictQuartzOptions + { + MaximumRefireCount = 5 + }); - // Act and assert - var exception = await Assert.ThrowsAsync(() => job.Execute(context)); + // Act and assert + var exception = await Assert.ThrowsAsync(() => job.Execute(context)); - Assert.False(exception.RefireImmediately); - } + Assert.False(exception.RefireImmediately); + } - private static OpenIddictQuartzJob CreateJob(IServiceProvider provider, OpenIddictQuartzOptions? options = null) - { - var scope = Mock.Of(scope => scope.ServiceProvider == provider); - var factory = Mock.Of(factory => factory.CreateScope() == scope); - var monitor = Mock.Of>( - monitor => monitor.CurrentValue == (options ?? new OpenIddictQuartzOptions())); + private static OpenIddictQuartzJob CreateJob(IServiceProvider provider, OpenIddictQuartzOptions? options = null) + { + var scope = Mock.Of(scope => scope.ServiceProvider == provider); + var factory = Mock.Of(factory => factory.CreateScope() == scope); + var monitor = Mock.Of>( + monitor => monitor.CurrentValue == (options ?? new OpenIddictQuartzOptions())); - return new OpenIddictQuartzJob(monitor, - Mock.Of(provider => provider.GetService(typeof(IServiceScopeFactory)) == factory)); - } + return new OpenIddictQuartzJob(monitor, + Mock.Of(provider => provider.GetService(typeof(IServiceScopeFactory)) == factory)); } } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs index db7edca7..739d6bdd 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTestServer.cs @@ -10,56 +10,55 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Hosting; using OpenIddict.Server.IntegrationTests; -namespace OpenIddict.Server.AspNetCore.IntegrationTests +namespace OpenIddict.Server.AspNetCore.IntegrationTests; + +/// +/// Represents a test host used by the server integration tests. +/// +public class OpenIddictServerAspNetCoreIntegrationTestServer : OpenIddictServerIntegrationTestServer { - /// - /// Represents a test host used by the server integration tests. - /// - public class OpenIddictServerAspNetCoreIntegrationTestServer : OpenIddictServerIntegrationTestServer - { #if SUPPORTS_GENERIC_HOST - public OpenIddictServerAspNetCoreIntegrationTestServer(IHost host) - { - Host = host; - Server = host.GetTestServer(); - } + public OpenIddictServerAspNetCoreIntegrationTestServer(IHost host) + { + Host = host; + Server = host.GetTestServer(); + } - /// - /// Gets the generic host used by this instance. - /// - public IHost Host { get; } + /// + /// Gets the generic host used by this instance. + /// + public IHost Host { get; } #else - public OpenIddictServerAspNetCoreIntegrationTestServer(TestServer server) - => Server = server; + public OpenIddictServerAspNetCoreIntegrationTestServer(TestServer server) + => Server = server; #endif - /// - /// Gets the ASP.NET Core test server used by this instance. - /// - public TestServer Server { get; } + /// + /// Gets the ASP.NET Core test server used by this instance. + /// + public TestServer Server { get; } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible of disposing the test client.")] - public override ValueTask CreateClientAsync() - => new ValueTask( - new OpenIddictServerIntegrationTestClient(Server.CreateClient())); + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The caller is responsible of disposing the test client.")] + public override ValueTask CreateClientAsync() + => new ValueTask( + new OpenIddictServerIntegrationTestClient(Server.CreateClient())); - public override + public override #if SUPPORTS_GENERIC_HOST - async + async #endif - ValueTask DisposeAsync() - { - // Dispose of the underlying test server. - Server.Dispose(); + ValueTask DisposeAsync() + { + // Dispose of the underlying test server. + Server.Dispose(); #if SUPPORTS_GENERIC_HOST - // Stop and dispose of the underlying generic host. - await Host.StopAsync(); - Host.Dispose(); + // Stop and dispose of the underlying generic host. + await Host.StopAsync(); + Host.Dispose(); #else - return default; + return default; #endif - } } -} \ No newline at end of file +} diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Authentication.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Authentication.cs index 0b1a79bd..407fdedb 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Authentication.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Authentication.cs @@ -12,51 +12,50 @@ using Xunit; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore.IntegrationTests +namespace OpenIddict.Server.AspNetCore.IntegrationTests; + +public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests + [Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] + public async Task ExtractAuthorizationRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() { - [Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] - public async Task ExtractAuthorizationRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2028(Parameters.RequestId), response.ErrorDescription); - } - - [Fact] - public async Task ExtractAuthorizationRequest_InvalidRequestIdParameterIsRejected() + RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2028(Parameters.RequestId), response.ErrorDescription); + } + + [Fact] + public async Task ExtractAuthorizationRequest_InvalidRequestIdParameterIsRejected() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.AddDistributedMemoryCache(); - - options.UseAspNetCore() - .EnableAuthorizationRequestCaching(); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.RequestId), response.ErrorDescription); - } + options.Services.AddDistributedMemoryCache(); + + options.UseAspNetCore() + .EnableAuthorizationRequestCaching(); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.RequestId), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Exchange.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Exchange.cs index d1927bb8..5c03b299 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Exchange.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Exchange.cs @@ -15,47 +15,46 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore.IntegrationTests +namespace OpenIddict.Server.AspNetCore.IntegrationTests; + +public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests + [Fact] + public async Task ExtractTokenRequest_MultipleClientCredentialsCauseAnError() { - [Fact] - public async Task ExtractTokenRequest_MultipleClientCredentialsCauseAnError() + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - var request = context.Transaction.GetHttpRequest()!; - request.Headers[HeaderNames.Authorization] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; + var request = context.Transaction.GetHttpRequest()!; + request.Headers[HeaderNames.Authorization] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; - return default; - }); - - builder.SetOrder(int.MinValue); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" + builder.SetOrder(int.MinValue); }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); - } + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Introspection.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Introspection.cs index 8c8f5c4f..bd0a7c45 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Introspection.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Introspection.cs @@ -14,45 +14,44 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore.IntegrationTests +namespace OpenIddict.Server.AspNetCore.IntegrationTests; + +public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests + [Fact] + public async Task ExtractIntrospectionRequest_MultipleClientCredentialsCauseAnError() { - [Fact] - public async Task ExtractIntrospectionRequest_MultipleClientCredentialsCauseAnError() + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - var request = context.Transaction.GetHttpRequest()!; - request.Headers[HeaderNames.Authorization] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; + var request = context.Transaction.GetHttpRequest()!; + request.Headers[HeaderNames.Authorization] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; - return default; - }); - - builder.SetOrder(int.MinValue); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA" + builder.SetOrder(int.MinValue); }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); - } + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Revocation.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Revocation.cs index 9c01037a..bc839988 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Revocation.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Revocation.cs @@ -14,45 +14,44 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore.IntegrationTests +namespace OpenIddict.Server.AspNetCore.IntegrationTests; + +public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests + [Fact] + public async Task ExtractRevocationRequest_MultipleClientCredentialsCauseAnError() { - [Fact] - public async Task ExtractRevocationRequest_MultipleClientCredentialsCauseAnError() + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - var request = context.Transaction.GetHttpRequest()!; - request.Headers[HeaderNames.Authorization] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; + var request = context.Transaction.GetHttpRequest()!; + request.Headers[HeaderNames.Authorization] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; - return default; - }); - - builder.SetOrder(int.MinValue); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA" + builder.SetOrder(int.MinValue); }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); - } + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Session.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Session.cs index be3a6588..be73fc9b 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Session.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Session.cs @@ -12,51 +12,50 @@ using Xunit; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore.IntegrationTests +namespace OpenIddict.Server.AspNetCore.IntegrationTests; + +public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests + [Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] + public async Task ExtractLogoutRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() { - [Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] - public async Task ExtractLogoutRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2028(Parameters.RequestId), response.ErrorDescription); - } - - [Fact] - public async Task ExtractLogoutRequest_InvalidRequestIdParameterIsRejected() + RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2028(Parameters.RequestId), response.ErrorDescription); + } + + [Fact] + public async Task ExtractLogoutRequest_InvalidRequestIdParameterIsRejected() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.AddDistributedMemoryCache(); - - options.UseAspNetCore() - .EnableLogoutRequestCaching(); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.RequestId), response.ErrorDescription); - } + options.Services.AddDistributedMemoryCache(); + + options.UseAspNetCore() + .EnableLogoutRequestCaching(); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest + { + RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.RequestId), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs index 24ae7351..79f2e487 100644 --- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs +++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs @@ -30,975 +30,974 @@ using static OpenIddict.Server.OpenIddictServerHandlers; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.AspNetCore.IntegrationTests +namespace OpenIddict.Server.AspNetCore.IntegrationTests; + +public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests + public OpenIddictServerAspNetCoreIntegrationTests(ITestOutputHelper outputHelper) + : base(outputHelper) { - public OpenIddictServerAspNetCoreIntegrationTests(ITestOutputHelper outputHelper) - : base(outputHelper) - { - } + } - [Fact] - public async Task ProcessAuthentication_CreationDateIsMappedToIssuedUtc() + [Fact] + public async Task ProcessAuthentication_CreationDateIsMappedToIssuedUtc() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate/properties"); + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate/properties"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetCreationDate(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero)); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetCreationDate(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero)); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest - { - AccessToken = "access_token" - }); + // Act + var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest + { + AccessToken = "access_token" + }); - // Assert - var properties = new AuthenticationProperties(response.GetParameters() - .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); - Assert.Equal(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.IssuedUtc); - } + Assert.Equal(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.IssuedUtc); + } - [Fact] - public async Task ProcessAuthentication_ExpirationDateIsMappedToIssuedUtc() + [Fact] + public async Task ProcessAuthentication_ExpirationDateIsMappedToIssuedUtc() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate/properties"); + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate/properties"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetExpirationDate(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero)); + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetExpirationDate(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero)); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - - await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - var properties = new AuthenticationProperties(response.GetParameters() - .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + await using var client = await server.CreateClientAsync(); - Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); - } - - [Fact] - public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties() + // Act + var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/challenge/custom"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); - return default; - })); - }); + Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); + } - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ProcessChallenge_ReturnsParametersFromAuthenticationProperties() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/challenge/custom"); - // Act - var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Assert - Assert.True((bool) response["boolean_parameter"]); - Assert.Equal(JsonValueKind.True, ((JsonElement) response["boolean_parameter"]).ValueKind); - Assert.Equal(42, (long) response["integer_parameter"]); - Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_parameter"]).ValueKind); - Assert.Equal("Bob l'Eponge", (string?) response["string_parameter"]); - Assert.Equal(JsonValueKind.String, ((JsonElement) response["string_parameter"]).ValueKind); - Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) response["array_parameter"]); - Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_parameter"]).ValueKind); - Assert.Equal("value", (string?) response["object_parameter"]?["parameter"]); - Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_parameter"]).ValueKind); - } + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessChallenge_ReturnsErrorFromAuthenticationProperties() + // Act + var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/challenge/custom"); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.True((bool) response["boolean_parameter"]); + Assert.Equal(JsonValueKind.True, ((JsonElement) response["boolean_parameter"]).ValueKind); + Assert.Equal(42, (long) response["integer_parameter"]); + Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_parameter"]).ValueKind); + Assert.Equal("Bob l'Eponge", (string?) response["string_parameter"]); + Assert.Equal(JsonValueKind.String, ((JsonElement) response["string_parameter"]).ValueKind); + Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) response["array_parameter"]); + Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_parameter"]).ValueKind); + Assert.Equal("value", (string?) response["object_parameter"]?["parameter"]); + Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_parameter"]).ValueKind); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + [Fact] + public async Task ProcessChallenge_ReturnsErrorFromAuthenticationProperties() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/challenge/custom"); - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal("custom_error", response.Error); - Assert.Equal("custom_error_description", response.ErrorDescription); - Assert.Equal("custom_error_uri", response.ErrorUri); - } + // Act + var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal("custom_error", response.Error); + Assert.Equal("custom_error_description", response.ErrorDescription); + Assert.Equal("custom_error_uri", response.ErrorUri); + } - [Theory] - [InlineData("/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/authorize", OpenIddictServerEndpointType.Authorization)] - [InlineData("/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] - [InlineData("/connect/authorize/", OpenIddictServerEndpointType.Authorization)] - [InlineData("/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] - [InlineData("/connect/authorize/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/AUTHORIZE/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/authorize/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/AUTHORIZE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] - [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] - [InlineData("/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] - [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] - [InlineData("/.well-known/openid-configuration/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.well-known/openid-configuration/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/.well-known/jwks/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.WELL-KNOWN/JWKS/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.well-known/jwks/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.WELL-KNOWN/JWKS/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/device", OpenIddictServerEndpointType.Device)] - [InlineData("/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] - [InlineData("/connect/device/", OpenIddictServerEndpointType.Device)] - [InlineData("/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] - [InlineData("/connect/device/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/DEVICE/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/device/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/DEVICE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/introspect", OpenIddictServerEndpointType.Introspection)] - [InlineData("/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] - [InlineData("/connect/introspect/", OpenIddictServerEndpointType.Introspection)] - [InlineData("/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] - [InlineData("/connect/introspect/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/INTROSPECT/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/introspect/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/INTROSPECT/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/logout", OpenIddictServerEndpointType.Logout)] - [InlineData("/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] - [InlineData("/connect/logout/", OpenIddictServerEndpointType.Logout)] - [InlineData("/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] - [InlineData("/connect/logout/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/LOGOUT/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/logout/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/LOGOUT/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/revoke", OpenIddictServerEndpointType.Revocation)] - [InlineData("/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] - [InlineData("/connect/revoke/", OpenIddictServerEndpointType.Revocation)] - [InlineData("/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] - [InlineData("/connect/revoke/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/REVOKE/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/revoke/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/REVOKE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/token", OpenIddictServerEndpointType.Token)] - [InlineData("/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] - [InlineData("/connect/token/", OpenIddictServerEndpointType.Token)] - [InlineData("/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] - [InlineData("/connect/token/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/TOKEN/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/token/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/TOKEN/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/connect/userinfo/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/USERINFO/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/userinfo/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/USERINFO/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/verification", OpenIddictServerEndpointType.Verification)] - [InlineData("/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] - [InlineData("/connect/verification/", OpenIddictServerEndpointType.Verification)] - [InlineData("/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] - [InlineData("/connect/verification/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/VERIFICATION/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/verification/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/VERIFICATION/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - public async Task ProcessRequest_MatchesCorrespondingRelativeEndpoint(string path, OpenIddictServerEndpointType type) + [Theory] + [InlineData("/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/authorize", OpenIddictServerEndpointType.Authorization)] + [InlineData("/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] + [InlineData("/connect/authorize/", OpenIddictServerEndpointType.Authorization)] + [InlineData("/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] + [InlineData("/connect/authorize/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/AUTHORIZE/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/authorize/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/AUTHORIZE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] + [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] + [InlineData("/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] + [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] + [InlineData("/.well-known/openid-configuration/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.well-known/openid-configuration/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/.well-known/jwks/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.WELL-KNOWN/JWKS/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.well-known/jwks/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.WELL-KNOWN/JWKS/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/device", OpenIddictServerEndpointType.Device)] + [InlineData("/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] + [InlineData("/connect/device/", OpenIddictServerEndpointType.Device)] + [InlineData("/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] + [InlineData("/connect/device/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/DEVICE/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/device/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/DEVICE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/introspect", OpenIddictServerEndpointType.Introspection)] + [InlineData("/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] + [InlineData("/connect/introspect/", OpenIddictServerEndpointType.Introspection)] + [InlineData("/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] + [InlineData("/connect/introspect/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/INTROSPECT/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/introspect/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/INTROSPECT/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/logout", OpenIddictServerEndpointType.Logout)] + [InlineData("/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] + [InlineData("/connect/logout/", OpenIddictServerEndpointType.Logout)] + [InlineData("/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] + [InlineData("/connect/logout/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/LOGOUT/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/logout/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/LOGOUT/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/revoke", OpenIddictServerEndpointType.Revocation)] + [InlineData("/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] + [InlineData("/connect/revoke/", OpenIddictServerEndpointType.Revocation)] + [InlineData("/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] + [InlineData("/connect/revoke/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/REVOKE/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/revoke/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/REVOKE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/token", OpenIddictServerEndpointType.Token)] + [InlineData("/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] + [InlineData("/connect/token/", OpenIddictServerEndpointType.Token)] + [InlineData("/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] + [InlineData("/connect/token/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/TOKEN/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/token/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/TOKEN/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/connect/userinfo/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/USERINFO/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/userinfo/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/USERINFO/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/verification", OpenIddictServerEndpointType.Verification)] + [InlineData("/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] + [InlineData("/connect/verification/", OpenIddictServerEndpointType.Verification)] + [InlineData("/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] + [InlineData("/connect/verification/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/VERIFICATION/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/verification/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/VERIFICATION/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + public async Task ProcessRequest_MatchesCorrespondingRelativeEndpoint(string path, OpenIddictServerEndpointType type) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - // Assert - Assert.Equal(type, context.EndpointType); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + // Assert + Assert.Equal(type, context.EndpointType); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - await client.PostAsync(path, new OpenIddictRequest()); - } + // Act + await client.PostAsync(path, new OpenIddictRequest()); + } - [Theory] - [InlineData("https://localhost/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:443/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST/CONNECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST/CONNECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:443/connect", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:443/connect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/authorize", OpenIddictServerEndpointType.Authorization)] - [InlineData("HTTPS://LOCALHOST/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] - [InlineData("https://localhost/connect/authorize/", OpenIddictServerEndpointType.Authorization)] - [InlineData("HTTPS://LOCALHOST/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] - [InlineData("https://localhost:443/connect/authorize", OpenIddictServerEndpointType.Authorization)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] - [InlineData("https://localhost:443/connect/authorize/", OpenIddictServerEndpointType.Authorization)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] - [InlineData("https://fabrikam.com/connect/authorize", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/authorize/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/authorize", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/authorize/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] - [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] - [InlineData("https://localhost/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] - [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] - [InlineData("https://localhost:443/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] - [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] - [InlineData("https://localhost:443/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] - [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] - [InlineData("https://fabrikam.com/.well-known/openid-configuration", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/.well-known/openid-configuration/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/.well-known/openid-configuration", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/.well-known/openid-configuration/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] - [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] - [InlineData("https://localhost/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("https://localhost:443/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] - [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] - [InlineData("https://localhost:443/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("https://fabrikam.com/.well-known/jwks", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/.well-known/jwks/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/.well-known/jwks", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/.well-known/jwks/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/device", OpenIddictServerEndpointType.Device)] - [InlineData("HTTPS://LOCALHOST/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] - [InlineData("https://localhost/connect/device/", OpenIddictServerEndpointType.Device)] - [InlineData("HTTPS://LOCALHOST/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] - [InlineData("https://localhost:443/connect/device", OpenIddictServerEndpointType.Device)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] - [InlineData("https://localhost:443/connect/device/", OpenIddictServerEndpointType.Device)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] - [InlineData("https://fabrikam.com/connect/device", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/DEVICE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/device/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/DEVICE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/device", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/DEVICE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/device/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/DEVICE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/introspect", OpenIddictServerEndpointType.Introspection)] - [InlineData("HTTPS://LOCALHOST/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] - [InlineData("https://localhost/connect/introspect/", OpenIddictServerEndpointType.Introspection)] - [InlineData("HTTPS://LOCALHOST/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] - [InlineData("https://localhost:443/connect/introspect", OpenIddictServerEndpointType.Introspection)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] - [InlineData("https://localhost:443/connect/introspect/", OpenIddictServerEndpointType.Introspection)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] - [InlineData("https://fabrikam.com/connect/introspect", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/introspect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/introspect", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/introspect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/logout", OpenIddictServerEndpointType.Logout)] - [InlineData("HTTPS://LOCALHOST/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] - [InlineData("https://localhost/connect/logout/", OpenIddictServerEndpointType.Logout)] - [InlineData("HTTPS://LOCALHOST/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] - [InlineData("https://localhost:443/connect/logout", OpenIddictServerEndpointType.Logout)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] - [InlineData("https://localhost:443/connect/logout/", OpenIddictServerEndpointType.Logout)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] - [InlineData("https://fabrikam.com/connect/logout", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/LOGOUT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/logout/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/logout", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/LOGOUT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/logout/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/revoke", OpenIddictServerEndpointType.Revocation)] - [InlineData("HTTPS://LOCALHOST/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] - [InlineData("https://localhost/connect/revoke/", OpenIddictServerEndpointType.Revocation)] - [InlineData("HTTPS://LOCALHOST/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] - [InlineData("https://localhost:443/connect/revoke", OpenIddictServerEndpointType.Revocation)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] - [InlineData("https://localhost:443/connect/revoke/", OpenIddictServerEndpointType.Revocation)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] - [InlineData("https://fabrikam.com/connect/revoke", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/REVOKE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/revoke/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/REVOKE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/revoke", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/REVOKE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/revoke/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/REVOKE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/token", OpenIddictServerEndpointType.Token)] - [InlineData("HTTPS://LOCALHOST/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] - [InlineData("https://localhost/connect/token/", OpenIddictServerEndpointType.Token)] - [InlineData("HTTPS://LOCALHOST/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] - [InlineData("https://localhost:443/connect/token", OpenIddictServerEndpointType.Token)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] - [InlineData("https://localhost:443/connect/token/", OpenIddictServerEndpointType.Token)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] - [InlineData("https://fabrikam.com/connect/token", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/TOKEN", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/token/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/TOKEN/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/token", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/TOKEN", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/token/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/TOKEN/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] - [InlineData("HTTPS://LOCALHOST/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] - [InlineData("https://localhost/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("HTTPS://LOCALHOST/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("https://localhost:443/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] - [InlineData("https://localhost:443/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("https://fabrikam.com/connect/userinfo", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/USERINFO", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/userinfo/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/USERINFO/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/userinfo", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/USERINFO", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/userinfo/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/USERINFO/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/verification", OpenIddictServerEndpointType.Verification)] - [InlineData("HTTPS://LOCALHOST/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] - [InlineData("https://localhost/connect/verification/", OpenIddictServerEndpointType.Verification)] - [InlineData("HTTPS://LOCALHOST/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] - [InlineData("https://localhost:443/connect/verification", OpenIddictServerEndpointType.Verification)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] - [InlineData("https://localhost:443/connect/verification/", OpenIddictServerEndpointType.Verification)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] - [InlineData("https://fabrikam.com/connect/verification", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/verification/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/verification", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/verification/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Unknown)] - public async Task ProcessRequest_MatchesCorrespondingAbsoluteEndpoint(string path, OpenIddictServerEndpointType type) + [Theory] + [InlineData("https://localhost/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:443/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST/CONNECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST/CONNECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:443/connect", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:443/connect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/authorize", OpenIddictServerEndpointType.Authorization)] + [InlineData("HTTPS://LOCALHOST/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] + [InlineData("https://localhost/connect/authorize/", OpenIddictServerEndpointType.Authorization)] + [InlineData("HTTPS://LOCALHOST/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] + [InlineData("https://localhost:443/connect/authorize", OpenIddictServerEndpointType.Authorization)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] + [InlineData("https://localhost:443/connect/authorize/", OpenIddictServerEndpointType.Authorization)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] + [InlineData("https://fabrikam.com/connect/authorize", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/authorize/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/authorize", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/authorize/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] + [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] + [InlineData("https://localhost/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] + [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] + [InlineData("https://localhost:443/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] + [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] + [InlineData("https://localhost:443/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] + [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] + [InlineData("https://fabrikam.com/.well-known/openid-configuration", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/.well-known/openid-configuration/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/.well-known/openid-configuration", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/.well-known/openid-configuration/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] + [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] + [InlineData("https://localhost/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("https://localhost:443/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] + [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] + [InlineData("https://localhost:443/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("https://fabrikam.com/.well-known/jwks", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/.well-known/jwks/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/.well-known/jwks", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/.well-known/jwks/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/device", OpenIddictServerEndpointType.Device)] + [InlineData("HTTPS://LOCALHOST/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] + [InlineData("https://localhost/connect/device/", OpenIddictServerEndpointType.Device)] + [InlineData("HTTPS://LOCALHOST/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] + [InlineData("https://localhost:443/connect/device", OpenIddictServerEndpointType.Device)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] + [InlineData("https://localhost:443/connect/device/", OpenIddictServerEndpointType.Device)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] + [InlineData("https://fabrikam.com/connect/device", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/DEVICE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/device/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/DEVICE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/device", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/DEVICE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/device/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/DEVICE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/introspect", OpenIddictServerEndpointType.Introspection)] + [InlineData("HTTPS://LOCALHOST/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] + [InlineData("https://localhost/connect/introspect/", OpenIddictServerEndpointType.Introspection)] + [InlineData("HTTPS://LOCALHOST/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] + [InlineData("https://localhost:443/connect/introspect", OpenIddictServerEndpointType.Introspection)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] + [InlineData("https://localhost:443/connect/introspect/", OpenIddictServerEndpointType.Introspection)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] + [InlineData("https://fabrikam.com/connect/introspect", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/introspect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/introspect", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/introspect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/logout", OpenIddictServerEndpointType.Logout)] + [InlineData("HTTPS://LOCALHOST/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] + [InlineData("https://localhost/connect/logout/", OpenIddictServerEndpointType.Logout)] + [InlineData("HTTPS://LOCALHOST/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] + [InlineData("https://localhost:443/connect/logout", OpenIddictServerEndpointType.Logout)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] + [InlineData("https://localhost:443/connect/logout/", OpenIddictServerEndpointType.Logout)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] + [InlineData("https://fabrikam.com/connect/logout", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/LOGOUT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/logout/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/logout", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/LOGOUT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/logout/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/revoke", OpenIddictServerEndpointType.Revocation)] + [InlineData("HTTPS://LOCALHOST/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] + [InlineData("https://localhost/connect/revoke/", OpenIddictServerEndpointType.Revocation)] + [InlineData("HTTPS://LOCALHOST/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] + [InlineData("https://localhost:443/connect/revoke", OpenIddictServerEndpointType.Revocation)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] + [InlineData("https://localhost:443/connect/revoke/", OpenIddictServerEndpointType.Revocation)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] + [InlineData("https://fabrikam.com/connect/revoke", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/REVOKE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/revoke/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/REVOKE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/revoke", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/REVOKE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/revoke/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/REVOKE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/token", OpenIddictServerEndpointType.Token)] + [InlineData("HTTPS://LOCALHOST/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] + [InlineData("https://localhost/connect/token/", OpenIddictServerEndpointType.Token)] + [InlineData("HTTPS://LOCALHOST/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] + [InlineData("https://localhost:443/connect/token", OpenIddictServerEndpointType.Token)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] + [InlineData("https://localhost:443/connect/token/", OpenIddictServerEndpointType.Token)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] + [InlineData("https://fabrikam.com/connect/token", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/TOKEN", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/token/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/TOKEN/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/token", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/TOKEN", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/token/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/TOKEN/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] + [InlineData("HTTPS://LOCALHOST/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] + [InlineData("https://localhost/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("HTTPS://LOCALHOST/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("https://localhost:443/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] + [InlineData("https://localhost:443/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("https://fabrikam.com/connect/userinfo", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/USERINFO", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/userinfo/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/USERINFO/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/userinfo", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/USERINFO", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/userinfo/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/USERINFO/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/verification", OpenIddictServerEndpointType.Verification)] + [InlineData("HTTPS://LOCALHOST/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] + [InlineData("https://localhost/connect/verification/", OpenIddictServerEndpointType.Verification)] + [InlineData("HTTPS://LOCALHOST/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] + [InlineData("https://localhost:443/connect/verification", OpenIddictServerEndpointType.Verification)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] + [InlineData("https://localhost:443/connect/verification/", OpenIddictServerEndpointType.Verification)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] + [InlineData("https://fabrikam.com/connect/verification", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/verification/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/verification", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/verification/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Unknown)] + public async Task ProcessRequest_MatchesCorrespondingAbsoluteEndpoint(string path, OpenIddictServerEndpointType type) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.SetAuthorizationEndpointUris("https://localhost/connect/authorize") - .SetConfigurationEndpointUris("https://localhost/.well-known/openid-configuration") - .SetCryptographyEndpointUris("https://localhost/.well-known/jwks") - .SetDeviceEndpointUris("https://localhost/connect/device") - .SetIntrospectionEndpointUris("https://localhost/connect/introspect") - .SetLogoutEndpointUris("https://localhost/connect/logout") - .SetRevocationEndpointUris("https://localhost/connect/revoke") - .SetTokenEndpointUris("https://localhost/connect/token") - .SetUserinfoEndpointUris("https://localhost/connect/userinfo") - .SetVerificationEndpointUris("https://localhost/connect/verification"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.EnableDegradedMode(); + + options.SetAuthorizationEndpointUris("https://localhost/connect/authorize") + .SetConfigurationEndpointUris("https://localhost/.well-known/openid-configuration") + .SetCryptographyEndpointUris("https://localhost/.well-known/jwks") + .SetDeviceEndpointUris("https://localhost/connect/device") + .SetIntrospectionEndpointUris("https://localhost/connect/introspect") + .SetLogoutEndpointUris("https://localhost/connect/logout") + .SetRevocationEndpointUris("https://localhost/connect/revoke") + .SetTokenEndpointUris("https://localhost/connect/token") + .SetUserinfoEndpointUris("https://localhost/connect/userinfo") + .SetVerificationEndpointUris("https://localhost/connect/verification"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - // Assert - Assert.Equal(type, context.EndpointType); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + // Assert + Assert.Equal(type, context.EndpointType); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - await client.PostAsync(path, new OpenIddictRequest()); - } + // Act + await client.PostAsync(path, new OpenIddictRequest()); + } - [Theory] - [InlineData("/custom/connect/authorize", OpenIddictServerEndpointType.Authorization)] - [InlineData("/custom/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] - [InlineData("/custom/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/custom/connect/device", OpenIddictServerEndpointType.Device)] - [InlineData("/custom/connect/custom", OpenIddictServerEndpointType.Unknown)] - [InlineData("/custom/connect/introspect", OpenIddictServerEndpointType.Introspection)] - [InlineData("/custom/connect/logout", OpenIddictServerEndpointType.Logout)] - [InlineData("/custom/connect/revoke", OpenIddictServerEndpointType.Revocation)] - [InlineData("/custom/connect/token", OpenIddictServerEndpointType.Token)] - [InlineData("/custom/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/custom/connect/verification", OpenIddictServerEndpointType.Verification)] - public async Task ProcessRequest_AllowsOverridingEndpoint(string address, OpenIddictServerEndpointType type) + [Theory] + [InlineData("/custom/connect/authorize", OpenIddictServerEndpointType.Authorization)] + [InlineData("/custom/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] + [InlineData("/custom/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/custom/connect/device", OpenIddictServerEndpointType.Device)] + [InlineData("/custom/connect/custom", OpenIddictServerEndpointType.Unknown)] + [InlineData("/custom/connect/introspect", OpenIddictServerEndpointType.Introspection)] + [InlineData("/custom/connect/logout", OpenIddictServerEndpointType.Logout)] + [InlineData("/custom/connect/revoke", OpenIddictServerEndpointType.Revocation)] + [InlineData("/custom/connect/token", OpenIddictServerEndpointType.Token)] + [InlineData("/custom/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/custom/connect/verification", OpenIddictServerEndpointType.Verification)] + public async Task ProcessRequest_AllowsOverridingEndpoint(string address, OpenIddictServerEndpointType type) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - // Act - context.EndpointType = type; - - // Assert - Assert.Equal(type, context.EndpointType); + // Act + context.EndpointType = type; - return default; - }); + // Assert + Assert.Equal(type, context.EndpointType); - builder.SetOrder(InferEndpointType.Descriptor.Order + 500); + return default; }); + + builder.SetOrder(InferEndpointType.Descriptor.Order + 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - await client.PostAsync(address, new OpenIddictRequest()); - } + // Act + await client.PostAsync(address, new OpenIddictRequest()); + } - [Theory] - [InlineData("/.well-known/openid-configuration")] - [InlineData("/.well-known/jwks")] - [InlineData("/connect/authorize")] - [InlineData("/connect/device")] - [InlineData("/connect/introspect")] - [InlineData("/connect/logout")] - [InlineData("/connect/revoke")] - [InlineData("/connect/token")] - [InlineData("/connect/userinfo")] - [InlineData("/connect/verification")] - public async Task ProcessRequest_RejectsInsecureHttpRequests(string address) + [Theory] + [InlineData("/.well-known/openid-configuration")] + [InlineData("/.well-known/jwks")] + [InlineData("/connect/authorize")] + [InlineData("/connect/device")] + [InlineData("/connect/introspect")] + [InlineData("/connect/logout")] + [InlineData("/connect/revoke")] + [InlineData("/connect/token")] + [InlineData("/connect/userinfo")] + [InlineData("/connect/verification")] + public async Task ProcessRequest_RejectsInsecureHttpRequests(string address) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.UseAspNetCore() - .Configure(options => options.DisableTransportSecurityRequirement = false); - }); + options.UseAspNetCore() + .Configure(options => options.DisableTransportSecurityRequirement = false); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync(address, new OpenIddictRequest()); + // Act + var response = await client.PostAsync(address, new OpenIddictRequest()); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2083), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2083), response.ErrorUri); - } + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2083), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2083), response.ErrorUri); + } - [Theory] - [InlineData("/.well-known/openid-configuration")] - [InlineData("/.well-known/jwks")] - [InlineData("/custom")] - [InlineData("/connect/authorize")] - [InlineData("/connect/device")] - [InlineData("/connect/introspect")] - [InlineData("/connect/logout")] - [InlineData("/connect/revoke")] - [InlineData("/connect/token")] - [InlineData("/connect/userinfo")] - [InlineData("/connect/verification")] - public async Task ProcessRequest_AllowsHandlingResponse(string address) + [Theory] + [InlineData("/.well-known/openid-configuration")] + [InlineData("/.well-known/jwks")] + [InlineData("/custom")] + [InlineData("/connect/authorize")] + [InlineData("/connect/device")] + [InlineData("/connect/introspect")] + [InlineData("/connect/logout")] + [InlineData("/connect/revoke")] + [InlineData("/connect/token")] + [InlineData("/connect/userinfo")] + [InlineData("/connect/verification")] + public async Task ProcessRequest_AllowsHandlingResponse(string address) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync(address, new OpenIddictRequest()); + // Act + var response = await client.PostAsync(address, new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Theory] - [InlineData("/.well-known/openid-configuration")] - [InlineData("/.well-known/jwks")] - [InlineData("/custom")] - [InlineData("/connect/authorize")] - [InlineData("/connect/device")] - [InlineData("/connect/introspect")] - [InlineData("/connect/logout")] - [InlineData("/connect/revoke")] - [InlineData("/connect/token")] - [InlineData("/connect/userinfo")] - [InlineData("/connect/verification")] - public async Task ProcessRequest_AllowsSkippingHandler(string address) + [Theory] + [InlineData("/.well-known/openid-configuration")] + [InlineData("/.well-known/jwks")] + [InlineData("/custom")] + [InlineData("/connect/authorize")] + [InlineData("/connect/device")] + [InlineData("/connect/introspect")] + [InlineData("/connect/logout")] + [InlineData("/connect/revoke")] + [InlineData("/connect/token")] + [InlineData("/connect/userinfo")] + [InlineData("/connect/verification")] + public async Task ProcessRequest_AllowsSkippingHandler(string address) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync(address, new OpenIddictRequest()); + // Act + var response = await client.PostAsync(address, new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task ProcessSignIn_ReturnsParametersFromAuthenticationProperties() + [Fact] + public async Task ProcessSignIn_ReturnsParametersFromAuthenticationProperties() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/signin/custom"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/signin/custom"); - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/signin/custom", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + return default; + })); + }); - // Assert - Assert.True((bool) response["boolean_parameter"]); - Assert.Equal(JsonValueKind.True, ((JsonElement) response["boolean_parameter"]).ValueKind); - Assert.Equal(42, (long) response["integer_parameter"]); - Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_parameter"]).ValueKind); - Assert.Equal("Bob l'Eponge", (string?) response["string_parameter"]); - Assert.Equal(JsonValueKind.String, ((JsonElement) response["string_parameter"]).ValueKind); - Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) response["array_parameter"]); - Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_parameter"]).ValueKind); - Assert.Equal("value", (string?) response["object_parameter"]?["parameter"]); - Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_parameter"]).ValueKind); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignOut_ReturnsParametersFromAuthenticationProperties() + // Act + var response = await client.PostAsync("/signin/custom", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetLogoutEndpointUris("/signout/custom"); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.True((bool) response["boolean_parameter"]); + Assert.Equal(JsonValueKind.True, ((JsonElement) response["boolean_parameter"]).ValueKind); + Assert.Equal(42, (long) response["integer_parameter"]); + Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_parameter"]).ValueKind); + Assert.Equal("Bob l'Eponge", (string?) response["string_parameter"]); + Assert.Equal(JsonValueKind.String, ((JsonElement) response["string_parameter"]).ValueKind); + Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) response["array_parameter"]); + Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_parameter"]).ValueKind); + Assert.Equal("value", (string?) response["object_parameter"]?["parameter"]); + Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_parameter"]).ValueKind); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + [Fact] + public async Task ProcessSignOut_ReturnsParametersFromAuthenticationProperties() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/signout/custom"); - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/signout/custom", new OpenIddictRequest - { - PostLogoutRedirectUri = "http://www.fabrikam.com/path", - State = "af0ifjsldkj" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.True((bool) response["boolean_parameter"]); - Assert.Equal(42, (long) response["integer_parameter"]); - Assert.Equal("Bob l'Eponge", (string?) response["string_parameter"]); - } + // Act + var response = await client.PostAsync("/signout/custom", new OpenIddictRequest + { + PostLogoutRedirectUri = "http://www.fabrikam.com/path", + State = "af0ifjsldkj" + }); + + // Assert + Assert.True((bool) response["boolean_parameter"]); + Assert.Equal(42, (long) response["integer_parameter"]); + Assert.Equal("Bob l'Eponge", (string?) response["string_parameter"]); + } - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible of disposing the test server.")] - protected override + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The caller is responsible of disposing the test server.")] + protected override #if SUPPORTS_GENERIC_HOST - async + async #endif - ValueTask CreateServerAsync(Action? configuration = null) - { + ValueTask CreateServerAsync(Action? configuration = null) + { #if SUPPORTS_GENERIC_HOST - var builder = new HostBuilder(); + var builder = new HostBuilder(); #else - var builder = new WebHostBuilder(); + var builder = new WebHostBuilder(); #endif - builder.UseEnvironment("Testing"); + builder.UseEnvironment("Testing"); - builder.ConfigureLogging(options => options.AddXUnit(OutputHelper)); + builder.ConfigureLogging(options => options.AddXUnit(OutputHelper)); - builder.ConfigureServices(ConfigureServices); - builder.ConfigureServices(services => - { - services.AddOpenIddict() - .AddServer(options => - { - // Disable the transport security requirement during testing. - options.UseAspNetCore() - .DisableTransportSecurityRequirement(); + builder.ConfigureServices(ConfigureServices); + builder.ConfigureServices(services => + { + services.AddOpenIddict() + .AddServer(options => + { + // Disable the transport security requirement during testing. + options.UseAspNetCore() + .DisableTransportSecurityRequirement(); - configuration?.Invoke(options); - }); - }); + configuration?.Invoke(options); + }); + }); #if SUPPORTS_GENERIC_HOST - builder.ConfigureWebHost(options => - { - options.UseTestServer(); - options.Configure(ConfigurePipeline); - }); + builder.ConfigureWebHost(options => + { + options.UseTestServer(); + options.Configure(ConfigurePipeline); + }); #else - builder.Configure(ConfigurePipeline); + builder.Configure(ConfigurePipeline); #endif #if SUPPORTS_GENERIC_HOST - var host = await builder.StartAsync(); + var host = await builder.StartAsync(); - return new OpenIddictServerAspNetCoreIntegrationTestServer(host); + return new OpenIddictServerAspNetCoreIntegrationTestServer(host); #else - var server = new TestServer(builder); + var server = new TestServer(builder); - return new ValueTask(new OpenIddictServerAspNetCoreIntegrationTestServer(server)); + return new ValueTask(new OpenIddictServerAspNetCoreIntegrationTestServer(server)); #endif - void ConfigurePipeline(IApplicationBuilder app) + void ConfigurePipeline(IApplicationBuilder app) + { + app.Use(next => async context => { - app.Use(next => async context => + await next(context); + + var feature = context.Features.Get(); + var response = feature?.Transaction?.GetProperty("custom_response"); + if (response is not null) { - await next(context); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(response)); + } + }); - var feature = context.Features.Get(); - var response = feature?.Transaction?.GetProperty("custom_response"); - if (response is not null) - { - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(response)); - } - }); + app.UseAuthentication(); - app.UseAuthentication(); + app.Use(next => async context => + { + if (context.Request.Path == "/signin") + { + var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + identity.AddClaim(Claims.Subject, "Bob le Bricoleur"); + + var principal = new ClaimsPrincipal(identity); + + await context.SignInAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal); + return; + } - app.Use(next => async context => + else if (context.Request.Path == "/signin/custom") { - if (context.Request.Path == "/signin") - { - var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - identity.AddClaim(Claims.Subject, "Bob le Bricoleur"); + var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + identity.AddClaim(Claims.Subject, "Bob le Bricoleur"); - var principal = new ClaimsPrincipal(identity); + var principal = new ClaimsPrincipal(identity); - await context.SignInAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal); - return; - } + var properties = new AuthenticationProperties( + items: new Dictionary(), + parameters: new Dictionary + { + ["boolean_parameter"] = true, + ["integer_parameter"] = 42, + ["string_parameter"] = "Bob l'Eponge", + ["array_parameter"] = JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]"), + ["object_parameter"] = JsonSerializer.Deserialize(@"{""parameter"":""value""}") + }); - else if (context.Request.Path == "/signin/custom") - { - var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - identity.AddClaim(Claims.Subject, "Bob le Bricoleur"); - - var principal = new ClaimsPrincipal(identity); - - var properties = new AuthenticationProperties( - items: new Dictionary(), - parameters: new Dictionary - { - ["boolean_parameter"] = true, - ["integer_parameter"] = 42, - ["string_parameter"] = "Bob l'Eponge", - ["array_parameter"] = JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]"), - ["object_parameter"] = JsonSerializer.Deserialize(@"{""parameter"":""value""}") - }); - - await context.SignInAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal, properties); - return; - } + await context.SignInAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal, properties); + return; + } - else if (context.Request.Path == "/signout") - { - await context.SignOutAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - return; - } + else if (context.Request.Path == "/signout") + { + await context.SignOutAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + return; + } - else if (context.Request.Path == "/signout/custom") - { - var properties = new AuthenticationProperties( - items: new Dictionary(), - parameters: new Dictionary - { - ["boolean_parameter"] = true, - ["integer_parameter"] = 42, - ["string_parameter"] = "Bob l'Eponge" - }); - - await context.SignOutAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties); - return; - } + else if (context.Request.Path == "/signout/custom") + { + var properties = new AuthenticationProperties( + items: new Dictionary(), + parameters: new Dictionary + { + ["boolean_parameter"] = true, + ["integer_parameter"] = 42, + ["string_parameter"] = "Bob l'Eponge" + }); - else if (context.Request.Path == "/challenge") - { - await context.ChallengeAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - return; - } + await context.SignOutAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties); + return; + } - else if (context.Request.Path == "/challenge/custom") - { - var properties = new AuthenticationProperties( - items: new Dictionary - { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = "custom_error", - [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "custom_error_description", - [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = "custom_error_uri" - }, - parameters: new Dictionary - { - ["boolean_parameter"] = true, - ["integer_parameter"] = 42, - ["string_parameter"] = "Bob l'Eponge", - ["array_parameter"] = JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]"), - ["object_parameter"] = JsonSerializer.Deserialize(@"{""parameter"":""value""}") - }); - - await context.ChallengeAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties); - return; - } + else if (context.Request.Path == "/challenge") + { + await context.ChallengeAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + return; + } - else if (context.Request.Path == "/authenticate") - { - var result = await context.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - if (result?.Principal is null) + else if (context.Request.Path == "/challenge/custom") + { + var properties = new AuthenticationProperties( + items: new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = "custom_error", + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "custom_error_description", + [OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = "custom_error_uri" + }, + parameters: new Dictionary { - return; - } + ["boolean_parameter"] = true, + ["integer_parameter"] = 42, + ["string_parameter"] = "Bob l'Eponge", + ["array_parameter"] = JsonSerializer.Deserialize(@"[""Contoso"",""Fabrikam""]"), + ["object_parameter"] = JsonSerializer.Deserialize(@"{""parameter"":""value""}") + }); - var claims = result.Principal.Claims.GroupBy(claim => claim.Type) - .Select(group => new KeyValuePair( - group.Key, group.Select(claim => claim.Value).ToArray())); + await context.ChallengeAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties); + return; + } - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(claims))); + else if (context.Request.Path == "/authenticate") + { + var result = await context.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + if (result?.Principal is null) + { return; } - else if (context.Request.Path == "/authenticate/properties") - { - var result = await context.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); - if (result?.Properties is null) - { - return; - } + var claims = result.Principal.Claims.GroupBy(claim => claim.Type) + .Select(group => new KeyValuePair( + group.Key, group.Select(claim => claim.Value).ToArray())); - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(result.Properties.Items))); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(claims))); + return; + } + + else if (context.Request.Path == "/authenticate/properties") + { + var result = await context.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + if (result?.Properties is null) + { return; } - await next(context); - }); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(result.Properties.Items))); + return; + } + + await next(context); + }); - app.Run(context => + app.Run(context => + { + context.Response.ContentType = "application/json"; + return context.Response.WriteAsync(JsonSerializer.Serialize(new { - context.Response.ContentType = "application/json"; - return context.Response.WriteAsync(JsonSerializer.Serialize(new - { - name = "Bob le Magnifique" - })); - }); - } + name = "Bob le Magnifique" + })); + }); } } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestClient.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestClient.cs index 78b93f55..b60c3eb9 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestClient.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestClient.cs @@ -18,502 +18,501 @@ using Microsoft.Extensions.Primitives; using OpenIddict.Abstractions; using static OpenIddict.Abstractions.OpenIddictConstants; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +/// +/// Exposes methods that allow sending OpenID Connect +/// requests and extracting the corresponding responses. +/// +public class OpenIddictServerIntegrationTestClient : IAsyncDisposable { /// - /// Exposes methods that allow sending OpenID Connect - /// requests and extracting the corresponding responses. + /// Initializes a new instance of the OpenID Connect client. + /// + public OpenIddictServerIntegrationTestClient() + : this(new HttpClient()) + { + } + + /// + /// Initializes a new instance of the OpenID Connect client. + /// + /// The HTTP client used to communicate with the OpenID Connect server. + public OpenIddictServerIntegrationTestClient(HttpClient client) + : this(client, new HtmlParser()) + { + } + + /// + /// Initializes a new instance of the OpenID Connect client. /// - public class OpenIddictServerIntegrationTestClient : IAsyncDisposable + /// The HTTP client used to communicate with the OpenID Connect server. + /// The HTML parser used to parse the responses returned by the OpenID Connect server. + public OpenIddictServerIntegrationTestClient(HttpClient client, HtmlParser parser) { - /// - /// Initializes a new instance of the OpenID Connect client. - /// - public OpenIddictServerIntegrationTestClient() - : this(new HttpClient()) + if (client is null) { + throw new ArgumentNullException(nameof(client)); } - /// - /// Initializes a new instance of the OpenID Connect client. - /// - /// The HTTP client used to communicate with the OpenID Connect server. - public OpenIddictServerIntegrationTestClient(HttpClient client) - : this(client, new HtmlParser()) + if (parser is null) { + throw new ArgumentNullException(nameof(parser)); } - /// - /// Initializes a new instance of the OpenID Connect client. - /// - /// The HTTP client used to communicate with the OpenID Connect server. - /// The HTML parser used to parse the responses returned by the OpenID Connect server. - public OpenIddictServerIntegrationTestClient(HttpClient client, HtmlParser parser) - { - if (client is null) - { - throw new ArgumentNullException(nameof(client)); - } + HttpClient = client; + HtmlParser = parser; + } - if (parser is null) - { - throw new ArgumentNullException(nameof(parser)); - } + /// + /// Gets the underlying HTTP client used to + /// communicate with the OpenID Connect server. + /// + public HttpClient HttpClient { get; } + + /// + /// Gets the underlying HTML parser used to parse the + /// responses returned by the OpenID Connect server. + /// + public HtmlParser HtmlParser { get; } + + /// + /// Sends an empty OpenID Connect request to the given endpoint using GET + /// and converts the returned response to an OpenID Connect response. + /// + /// The endpoint to which the request is sent. + /// The OpenID Connect response returned by the server. + public Task GetAsync(string uri) + => GetAsync(uri, new OpenIddictRequest()); + + /// + /// Sends an empty OpenID Connect request to the given endpoint using GET + /// and converts the returned response to an OpenID Connect response. + /// + /// The endpoint to which the request is sent. + /// The OpenID Connect response returned by the server. + public Task GetAsync(Uri uri) + => GetAsync(uri, new OpenIddictRequest()); - HttpClient = client; - HtmlParser = parser; + /// + /// Sends a generic OpenID Connect request to the given endpoint using GET + /// and converts the returned response to an OpenID Connect response. + /// + /// The endpoint to which the request is sent. + /// The OpenID Connect request to send. + /// The OpenID Connect response returned by the server. + public Task GetAsync(string uri, OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Gets the underlying HTTP client used to - /// communicate with the OpenID Connect server. - /// - public HttpClient HttpClient { get; } - - /// - /// Gets the underlying HTML parser used to parse the - /// responses returned by the OpenID Connect server. - /// - public HtmlParser HtmlParser { get; } - - /// - /// Sends an empty OpenID Connect request to the given endpoint using GET - /// and converts the returned response to an OpenID Connect response. - /// - /// The endpoint to which the request is sent. - /// The OpenID Connect response returned by the server. - public Task GetAsync(string uri) - => GetAsync(uri, new OpenIddictRequest()); - - /// - /// Sends an empty OpenID Connect request to the given endpoint using GET - /// and converts the returned response to an OpenID Connect response. - /// - /// The endpoint to which the request is sent. - /// The OpenID Connect response returned by the server. - public Task GetAsync(Uri uri) - => GetAsync(uri, new OpenIddictRequest()); - - /// - /// Sends a generic OpenID Connect request to the given endpoint using GET - /// and converts the returned response to an OpenID Connect response. - /// - /// The endpoint to which the request is sent. - /// The OpenID Connect request to send. - /// The OpenID Connect response returned by the server. - public Task GetAsync(string uri, OpenIddictRequest request) + if (string.IsNullOrEmpty(uri)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); + } - if (string.IsNullOrEmpty(uri)) - { - throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); - } + return GetAsync(new Uri(uri, UriKind.RelativeOrAbsolute), request); + } + + /// + /// Sends a generic OpenID Connect request to the given endpoint using GET + /// and converts the returned response to an OpenID Connect response. + /// + /// The endpoint to which the request is sent. + /// The OpenID Connect request to send. + /// The OpenID Connect response returned by the server. + public Task GetAsync(Uri uri, OpenIddictRequest request) + => SendAsync(HttpMethod.Get, uri, request); - return GetAsync(new Uri(uri, UriKind.RelativeOrAbsolute), request); + /// + /// Sends a generic OpenID Connect request to the given endpoint using POST + /// and converts the returned response to an OpenID Connect response. + /// + /// The endpoint to which the request is sent. + /// The OpenID Connect request to send. + /// The OpenID Connect response returned by the server. + public Task PostAsync(string uri, OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Sends a generic OpenID Connect request to the given endpoint using GET - /// and converts the returned response to an OpenID Connect response. - /// - /// The endpoint to which the request is sent. - /// The OpenID Connect request to send. - /// The OpenID Connect response returned by the server. - public Task GetAsync(Uri uri, OpenIddictRequest request) - => SendAsync(HttpMethod.Get, uri, request); - - /// - /// Sends a generic OpenID Connect request to the given endpoint using POST - /// and converts the returned response to an OpenID Connect response. - /// - /// The endpoint to which the request is sent. - /// The OpenID Connect request to send. - /// The OpenID Connect response returned by the server. - public Task PostAsync(string uri, OpenIddictRequest request) + if (string.IsNullOrEmpty(uri)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); + } - if (string.IsNullOrEmpty(uri)) - { - throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); - } + return PostAsync(new Uri(uri, UriKind.RelativeOrAbsolute), request); + } + + /// + /// Sends a generic OpenID Connect request to the given endpoint using POST + /// and converts the returned response to an OpenID Connect response. + /// + /// The endpoint to which the request is sent. + /// The OpenID Connect request to send. + /// The OpenID Connect response returned by the server. + public Task PostAsync(Uri uri, OpenIddictRequest request) + => SendAsync(HttpMethod.Post, uri, request); - return PostAsync(new Uri(uri, UriKind.RelativeOrAbsolute), request); + /// + /// Sends a generic OpenID Connect request to the given endpoint and + /// converts the returned response to an OpenID Connect response. + /// + /// The HTTP method used to send the OpenID Connect request. + /// The endpoint to which the request is sent. + /// The OpenID Connect request to send. + /// The OpenID Connect response returned by the server. + public Task SendAsync(string method, string uri, OpenIddictRequest request) + { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); } - /// - /// Sends a generic OpenID Connect request to the given endpoint using POST - /// and converts the returned response to an OpenID Connect response. - /// - /// The endpoint to which the request is sent. - /// The OpenID Connect request to send. - /// The OpenID Connect response returned by the server. - public Task PostAsync(Uri uri, OpenIddictRequest request) - => SendAsync(HttpMethod.Post, uri, request); - - /// - /// Sends a generic OpenID Connect request to the given endpoint and - /// converts the returned response to an OpenID Connect response. - /// - /// The HTTP method used to send the OpenID Connect request. - /// The endpoint to which the request is sent. - /// The OpenID Connect request to send. - /// The OpenID Connect response returned by the server. - public Task SendAsync(string method, string uri, OpenIddictRequest request) + if (string.IsNullOrEmpty(method)) { - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + throw new ArgumentException("The HTTP method cannot be null or empty.", nameof(method)); + } - if (string.IsNullOrEmpty(method)) - { - throw new ArgumentException("The HTTP method cannot be null or empty.", nameof(method)); - } + if (string.IsNullOrEmpty(uri)) + { + throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); + } - if (string.IsNullOrEmpty(uri)) - { - throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); - } + return SendAsync(new HttpMethod(method), uri, request); + } - return SendAsync(new HttpMethod(method), uri, request); + /// + /// Sends a generic OpenID Connect request to the given endpoint and + /// converts the returned response to an OpenID Connect response. + /// + /// The HTTP method used to send the OpenID Connect request. + /// The endpoint to which the request is sent. + /// The OpenID Connect request to send. + /// The OpenID Connect response returned by the server. + public Task SendAsync(HttpMethod method, string uri, OpenIddictRequest request) + { + if (method is null) + { + throw new ArgumentNullException(nameof(method)); } - /// - /// Sends a generic OpenID Connect request to the given endpoint and - /// converts the returned response to an OpenID Connect response. - /// - /// The HTTP method used to send the OpenID Connect request. - /// The endpoint to which the request is sent. - /// The OpenID Connect request to send. - /// The OpenID Connect response returned by the server. - public Task SendAsync(HttpMethod method, string uri, OpenIddictRequest request) + if (request is null) { - if (method is null) - { - throw new ArgumentNullException(nameof(method)); - } + throw new ArgumentNullException(nameof(request)); + } - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } + if (string.IsNullOrEmpty(uri)) + { + throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); + } - if (string.IsNullOrEmpty(uri)) - { - throw new ArgumentException("The URL cannot be null or empty.", nameof(uri)); - } + return SendAsync(method, new Uri(uri, UriKind.RelativeOrAbsolute), request); + } - return SendAsync(method, new Uri(uri, UriKind.RelativeOrAbsolute), request); + /// + /// Sends a generic OpenID Connect request to the given endpoint and + /// converts the returned response to an OpenID Connect response. + /// + /// The HTTP method used to send the OpenID Connect request. + /// The endpoint to which the request is sent. + /// The OpenID Connect request to send. + /// The OpenID Connect response returned by the server. + public virtual async Task SendAsync(HttpMethod method, Uri uri, OpenIddictRequest request) + { + if (method is null) + { + throw new ArgumentNullException(nameof(method)); } - /// - /// Sends a generic OpenID Connect request to the given endpoint and - /// converts the returned response to an OpenID Connect response. - /// - /// The HTTP method used to send the OpenID Connect request. - /// The endpoint to which the request is sent. - /// The OpenID Connect request to send. - /// The OpenID Connect response returned by the server. - public virtual async Task SendAsync(HttpMethod method, Uri uri, OpenIddictRequest request) + if (uri is null) { - if (method is null) - { - throw new ArgumentNullException(nameof(method)); - } + throw new ArgumentNullException(nameof(uri)); + } + + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (HttpClient.BaseAddress is null && !uri.IsAbsoluteUri) + { + throw new ArgumentException("The address cannot be a relative URI when no base address " + + "is associated with the HTTP client.", nameof(uri)); + } + + using var message = CreateRequestMessage(request, method, uri); + using var response = await HttpClient.SendAsync(message); + + return await GetResponseAsync(response); + } - if (uri is null) + private HttpRequestMessage CreateRequestMessage(OpenIddictRequest request, HttpMethod method, Uri uri) + { + // Note: a dictionary is deliberately not used here to allow multiple parameters with the + // same name to be specified. While initially not allowed by the core OAuth2 specification, + // this is required for derived drafts like the OAuth2 token exchange specification. + var parameters = new List>(); + + foreach (var parameter in request.GetParameters()) + { + // If the parameter is null or empty, send an empty value. + if (OpenIddictParameter.IsNullOrEmpty(parameter.Value)) { - throw new ArgumentNullException(nameof(uri)); + parameters.Add(new KeyValuePair(parameter.Key, string.Empty)); + + continue; } - if (request is null) + var values = (string?[]?) parameter.Value; + if (values is null || values.Length == 0) { - throw new ArgumentNullException(nameof(request)); + continue; } - if (HttpClient.BaseAddress is null && !uri.IsAbsoluteUri) + foreach (var value in values) { - throw new ArgumentException("The address cannot be a relative URI when no base address " + - "is associated with the HTTP client.", nameof(uri)); + parameters.Add(new KeyValuePair(parameter.Key, value)); } - - using var message = CreateRequestMessage(request, method, uri); - using var response = await HttpClient.SendAsync(message); - - return await GetResponseAsync(response); } - private HttpRequestMessage CreateRequestMessage(OpenIddictRequest request, HttpMethod method, Uri uri) + if (method == HttpMethod.Get && parameters.Count != 0) { - // Note: a dictionary is deliberately not used here to allow multiple parameters with the - // same name to be specified. While initially not allowed by the core OAuth2 specification, - // this is required for derived drafts like the OAuth2 token exchange specification. - var parameters = new List>(); + var builder = new StringBuilder(); - foreach (var parameter in request.GetParameters()) + foreach (var parameter in parameters) { - // If the parameter is null or empty, send an empty value. - if (OpenIddictParameter.IsNullOrEmpty(parameter.Value)) + if (string.IsNullOrEmpty(parameter.Key)) { - parameters.Add(new KeyValuePair(parameter.Key, string.Empty)); - continue; } - var values = (string?[]?) parameter.Value; - if (values is null || values.Length == 0) + if (builder.Length != 0) { - continue; + builder.Append('&'); } - foreach (var value in values) + builder.Append(UrlEncoder.Default.Encode(parameter.Key)); + + if (!string.IsNullOrEmpty(parameter.Value)) { - parameters.Add(new KeyValuePair(parameter.Key, value)); + builder.Append('='); + builder.Append(UrlEncoder.Default.Encode(parameter.Value)); } } - if (method == HttpMethod.Get && parameters.Count != 0) + if (!uri.IsAbsoluteUri) { - var builder = new StringBuilder(); + uri = new Uri(HttpClient.BaseAddress!, uri); + } + + uri = new UriBuilder(uri) { Query = builder.ToString() }.Uri; + } + + var message = new HttpRequestMessage(method, uri); + + if (method != HttpMethod.Get) + { + message.Content = new FormUrlEncodedContent(parameters); + } + + return message; + } + + private async Task GetResponseAsync(HttpResponseMessage message) + { + if (message.Headers.WwwAuthenticate.Count != 0) + { + var response = new OpenIddictResponse(); - foreach (var parameter in parameters) + foreach (var header in message.Headers.WwwAuthenticate) + { + if (string.IsNullOrEmpty(header.Parameter)) { - if (string.IsNullOrEmpty(parameter.Key)) + continue; + } + + foreach (var parameter in header.Parameter.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + var values = parameter.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + if (values.Length != 2) { continue; } - if (builder.Length != 0) + var name = values[0]?.Trim(' ', '"'); + if (string.IsNullOrEmpty(name)) { - builder.Append('&'); + continue; } - builder.Append(UrlEncoder.Default.Encode(parameter.Key)); - - if (!string.IsNullOrEmpty(parameter.Value)) + var value = values[1]?.Trim(' ', '"'); + if (string.IsNullOrEmpty(name)) { - builder.Append('='); - builder.Append(UrlEncoder.Default.Encode(parameter.Value)); + continue; } - } - if (!uri.IsAbsoluteUri) - { - uri = new Uri(HttpClient.BaseAddress!, uri); + response.SetParameter(name, value); } - - uri = new UriBuilder(uri) { Query = builder.ToString() }.Uri; } - var message = new HttpRequestMessage(method, uri); + return response; + } - if (method != HttpMethod.Get) + else if (message.Headers.Location is not null) + { + var payload = message.Headers.Location.Fragment; + if (string.IsNullOrEmpty(payload)) { - message.Content = new FormUrlEncodedContent(parameters); + payload = message.Headers.Location.Query; } - return message; - } - - private async Task GetResponseAsync(HttpResponseMessage message) - { - if (message.Headers.WwwAuthenticate.Count != 0) + if (string.IsNullOrEmpty(payload)) { - var response = new OpenIddictResponse(); + return new OpenIddictResponse(); + } - foreach (var header in message.Headers.WwwAuthenticate) + static string? UnescapeDataString(string value) + { + if (string.IsNullOrEmpty(value)) { - if (string.IsNullOrEmpty(header.Parameter)) - { - continue; - } - - foreach (var parameter in header.Parameter.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) - { - var values = parameter.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); - if (values.Length != 2) - { - continue; - } - - var name = values[0]?.Trim(' ', '"'); - if (string.IsNullOrEmpty(name)) - { - continue; - } - - var value = values[1]?.Trim(' ', '"'); - if (string.IsNullOrEmpty(name)) - { - continue; - } - - response.SetParameter(name, value); - } + return null; } - return response; + return Uri.UnescapeDataString(value.Replace("+", "%20")); } - else if (message.Headers.Location is not null) + // Note: a dictionary is deliberately not used here to allow multiple parameters with the + // same name to be retrieved. While initially not allowed by the core OAuth2 specification, + // this is required for derived drafts like the OAuth2 token exchange specification. + var parameters = new List>(); + + foreach (var element in new StringTokenizer(payload, Separators.Ampersand)) { - var payload = message.Headers.Location.Fragment; - if (string.IsNullOrEmpty(payload)) + var segment = element; + if (segment.Length == 0) { - payload = message.Headers.Location.Query; + continue; } - if (string.IsNullOrEmpty(payload)) + // Always skip the first char (# or ?). + if (segment.Offset == 0) { - return new OpenIddictResponse(); + segment = segment.Subsegment(1, segment.Length - 1); } - static string? UnescapeDataString(string value) + var index = segment.IndexOf('='); + if (index == -1) { - if (string.IsNullOrEmpty(value)) - { - return null; - } + continue; + } - return Uri.UnescapeDataString(value.Replace("+", "%20")); + var name = UnescapeDataString(segment.Substring(0, index)); + if (string.IsNullOrEmpty(name)) + { + continue; } - // Note: a dictionary is deliberately not used here to allow multiple parameters with the - // same name to be retrieved. While initially not allowed by the core OAuth2 specification, - // this is required for derived drafts like the OAuth2 token exchange specification. - var parameters = new List>(); + var value = UnescapeDataString(segment.Substring(index + 1, segment.Length - (index + 1))); - foreach (var element in new StringTokenizer(payload, Separators.Ampersand)) - { - var segment = element; - if (segment.Length == 0) - { - continue; - } + parameters.Add(new KeyValuePair(name, value)); + } - // Always skip the first char (# or ?). - if (segment.Offset == 0) - { - segment = segment.Subsegment(1, segment.Length - 1); - } + return new OpenIddictResponse( + from parameter in parameters + group parameter by parameter.Key into grouping + let values = grouping.Select(parameter => parameter.Value) + select new KeyValuePair(grouping.Key, values.ToArray())); + } - var index = segment.IndexOf('='); - if (index == -1) - { - continue; - } + else if (string.Equals(message.Content?.Headers?.ContentType?.MediaType, "application/json", StringComparison.OrdinalIgnoreCase)) + { + return (await message.Content!.ReadFromJsonAsync())!; + } - var name = UnescapeDataString(segment.Substring(0, index)); - if (string.IsNullOrEmpty(name)) - { - continue; - } + else if (string.Equals(message.Content?.Headers?.ContentType?.MediaType, "text/html", StringComparison.OrdinalIgnoreCase)) + { + // Note: this test client is only used with OpenIddict's ASP.NET Core or OWIN hosts, + // that always return their HTTP responses encoded using UTF-8. As such, the stream + // returned by ReadAsStreamAsync() is always assumed to contain UTF-8 encoded payloads. + using var stream = await message.Content!.ReadAsStreamAsync(); + using var document = await HtmlParser.ParseDocumentAsync(stream); - var value = UnescapeDataString(segment.Substring(index + 1, segment.Length - (index + 1))); + // Note: a dictionary is deliberately not used here to allow multiple parameters with the + // same name to be retrieved. While initially not allowed by the core OAuth2 specification, + // this is required for derived drafts like the OAuth2 token exchange specification. + var parameters = new List>(); - parameters.Add(new KeyValuePair(name, value)); + foreach (var element in document.Body.GetElementsByTagName("input")) + { + var name = element.GetAttribute("name"); + if (string.IsNullOrEmpty(name)) + { + continue; } - return new OpenIddictResponse( - from parameter in parameters - group parameter by parameter.Key into grouping - let values = grouping.Select(parameter => parameter.Value) - select new KeyValuePair(grouping.Key, values.ToArray())); - } + var value = element.GetAttribute("value"); - else if (string.Equals(message.Content?.Headers?.ContentType?.MediaType, "application/json", StringComparison.OrdinalIgnoreCase)) - { - return (await message.Content!.ReadFromJsonAsync())!; + parameters.Add(new KeyValuePair(name, value)); } - else if (string.Equals(message.Content?.Headers?.ContentType?.MediaType, "text/html", StringComparison.OrdinalIgnoreCase)) - { - // Note: this test client is only used with OpenIddict's ASP.NET Core or OWIN hosts, - // that always return their HTTP responses encoded using UTF-8. As such, the stream - // returned by ReadAsStreamAsync() is always assumed to contain UTF-8 encoded payloads. - using var stream = await message.Content!.ReadAsStreamAsync(); - using var document = await HtmlParser.ParseDocumentAsync(stream); - - // Note: a dictionary is deliberately not used here to allow multiple parameters with the - // same name to be retrieved. While initially not allowed by the core OAuth2 specification, - // this is required for derived drafts like the OAuth2 token exchange specification. - var parameters = new List>(); - - foreach (var element in document.Body.GetElementsByTagName("input")) - { - var name = element.GetAttribute("name"); - if (string.IsNullOrEmpty(name)) - { - continue; - } - - var value = element.GetAttribute("value"); + return new OpenIddictResponse( + from parameter in parameters + group parameter by parameter.Key into grouping + let values = grouping.Select(parameter => parameter.Value) + select new KeyValuePair(grouping.Key, values.ToArray())); + } - parameters.Add(new KeyValuePair(name, value)); - } + else if (string.Equals(message.Content?.Headers?.ContentType?.MediaType, "text/plain", StringComparison.OrdinalIgnoreCase)) + { + // Note: this test client is only used with OpenIddict's ASP.NET Core or OWIN hosts, + // that always return their HTTP responses encoded using UTF-8. As such, the stream + // returned by ReadAsStreamAsync() is always assumed to contain UTF-8 encoded payloads. + using var stream = await message.Content!.ReadAsStreamAsync(); + using var reader = new StreamReader(stream); - return new OpenIddictResponse( - from parameter in parameters - group parameter by parameter.Key into grouping - let values = grouping.Select(parameter => parameter.Value) - select new KeyValuePair(grouping.Key, values.ToArray())); - } + // Note: a dictionary is deliberately not used here to allow multiple parameters with the + // same name to be retrieved. While initially not allowed by the core OAuth2 specification, + // this is required for derived drafts like the OAuth2 token exchange specification. + var parameters = new List>(); - else if (string.Equals(message.Content?.Headers?.ContentType?.MediaType, "text/plain", StringComparison.OrdinalIgnoreCase)) + for (var line = await reader.ReadLineAsync(); line is not null; line = await reader.ReadLineAsync()) { - // Note: this test client is only used with OpenIddict's ASP.NET Core or OWIN hosts, - // that always return their HTTP responses encoded using UTF-8. As such, the stream - // returned by ReadAsStreamAsync() is always assumed to contain UTF-8 encoded payloads. - using var stream = await message.Content!.ReadAsStreamAsync(); - using var reader = new StreamReader(stream); - - // Note: a dictionary is deliberately not used here to allow multiple parameters with the - // same name to be retrieved. While initially not allowed by the core OAuth2 specification, - // this is required for derived drafts like the OAuth2 token exchange specification. - var parameters = new List>(); - - for (var line = await reader.ReadLineAsync(); line is not null; line = await reader.ReadLineAsync()) + var index = line.IndexOf(':'); + if (index == -1) { - var index = line.IndexOf(':'); - if (index == -1) - { - continue; - } - - var name = line.Substring(0, index); - if (string.IsNullOrEmpty(name)) - { - continue; - } - - var value = line.Substring(index + 1); + continue; + } - parameters.Add(new KeyValuePair(name, value)); + var name = line.Substring(0, index); + if (string.IsNullOrEmpty(name)) + { + continue; } - return new OpenIddictResponse( - from parameter in parameters - group parameter by parameter.Key into grouping - let values = grouping.Select(parameter => parameter.Value) - select new KeyValuePair(grouping.Key, values.ToArray())); + var value = line.Substring(index + 1); + + parameters.Add(new KeyValuePair(name, value)); } - return new OpenIddictResponse(); + return new OpenIddictResponse( + from parameter in parameters + group parameter by parameter.Key into grouping + let values = grouping.Select(parameter => parameter.Value) + select new KeyValuePair(grouping.Key, values.ToArray())); } - public ValueTask DisposeAsync() - { - HttpClient.Dispose(); + return new OpenIddictResponse(); + } - return default; - } + public ValueTask DisposeAsync() + { + HttpClient.Dispose(); + + return default; } -} \ No newline at end of file +} diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestServer.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestServer.cs index d8a6f4db..6c35c0e5 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestServer.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTestServer.cs @@ -7,15 +7,14 @@ using System; using System.Threading.Tasks; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +/// +/// Represents a test host used by the server integration tests. +/// +public abstract class OpenIddictServerIntegrationTestServer : IAsyncDisposable { - /// - /// Represents a test host used by the server integration tests. - /// - public abstract class OpenIddictServerIntegrationTestServer : IAsyncDisposable - { - public abstract ValueTask CreateClientAsync(); + public abstract ValueTask CreateClientAsync(); - public virtual ValueTask DisposeAsync() => default; - } -} \ No newline at end of file + public virtual ValueTask DisposeAsync() => default; +} diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs index 7ce6437e..8ffa6103 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs @@ -19,2329 +19,2328 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests - { - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractAuthorizationRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/connect/authorize", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Fact] - public async Task ExtractAuthorizationRequest_UnsupportedRequestParameterIsRejected() - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - Request = "eyJhbGciOiJub25lIn0.eyJpc3MiOiJodHRwOi8vd3d3LmZhYnJpa2FtLmNvbSIsImF1ZCI6Imh0" + - "dHA6Ly93d3cuY29udG9zby5jb20iLCJyZXNwb25zZV90eXBlIjoiY29kZSIsImNsaWVudF9pZCI6" + - "IkZhYnJpa2FtIiwicmVkaXJlY3RfdXJpIjoiaHR0cDovL3d3dy5mYWJyaWthbS5jb20vcGF0aCJ9.", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractAuthorizationRequest_UnexpectedMethodReturnsAnError(string method) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.RequestNotSupported, response.Error); - Assert.Equal(SR.FormatID2028(Parameters.Request), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2028), response.ErrorUri); - } + // Act + var response = await client.SendAsync(method, "/connect/authorize", new OpenIddictRequest()); - [Fact] - public async Task ExtractAuthorizationRequest_UnsupportedRequestUriParameterIsRejected() - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - RequestUri = "http://www.fabrikam.com/request/GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + [Fact] + public async Task ExtractAuthorizationRequest_UnsupportedRequestParameterIsRejected() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + Request = "eyJhbGciOiJub25lIn0.eyJpc3MiOiJodHRwOi8vd3d3LmZhYnJpa2FtLmNvbSIsImF1ZCI6Imh0" + + "dHA6Ly93d3cuY29udG9zby5jb20iLCJyZXNwb25zZV90eXBlIjoiY29kZSIsImNsaWVudF9pZCI6" + + "IkZhYnJpa2FtIiwicmVkaXJlY3RfdXJpIjoiaHR0cDovL3d3dy5mYWJyaWthbS5jb20vcGF0aCJ9.", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.RequestNotSupported, response.Error); + Assert.Equal(SR.FormatID2028(Parameters.Request), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2028), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.RequestUriNotSupported, response.Error); - Assert.Equal(SR.FormatID2028(Parameters.RequestUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2028), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractAuthorizationRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + [Fact] + public async Task ExtractAuthorizationRequest_UnsupportedRequestUriParameterIsRejected() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + RequestUri = "http://www.fabrikam.com/request/GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.RequestUriNotSupported, response.Error); + Assert.Equal(SR.FormatID2028(Parameters.RequestUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2028), response.ErrorUri); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractAuthorizationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.GetAsync("/connect/authorize"); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Act + var response = await client.GetAsync("/connect/authorize"); - [Fact] - public async Task ExtractAuthorizationRequest_AllowsHandlingResponse() + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } + + [Fact] + public async Task ExtractAuthorizationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/connect/authorize"); + // Act + var response = await client.GetAsync("/connect/authorize"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractAuthorizationRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractAuthorizationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - }); + options.EnableDegradedMode(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.GetAsync("/connect/authorize"); + return default; + })); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateAuthorizationRequest_MissingClientIdCausesAnError() - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.GetAsync("/connect/authorize"); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = null - }); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + [Fact] + public async Task ValidateAuthorizationRequest_MissingClientIdCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateAuthorizationRequest_MissingRedirectUriCausesAnErrorForOpenIdRequests() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + ClientId = null + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = null, - Scope = Scopes.OpenId - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.RedirectUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } - - [Theory] - [InlineData("/path", SR.ID2030)] - [InlineData("/tmp/file.xml", SR.ID2030)] - [InlineData("C:\\tmp\\file.xml", SR.ID2030)] - [InlineData("http://www.fabrikam.com/path#param=value", SR.ID2031)] - public async Task ValidateAuthorizationRequest_InvalidRedirectUriCausesAnError(string address, string message) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = address, - Scope = Scopes.OpenId - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(string.Format(SR.GetResourceString(message), Parameters.RedirectUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(message), response.ErrorUri); - } + [Fact] + public async Task ValidateAuthorizationRequest_MissingRedirectUriCausesAnErrorForOpenIdRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = null, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.RedirectUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - [Fact] - public async Task ValidateAuthorizationRequest_MissingResponseTypeCausesAnError() - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + [Theory] + [InlineData("/path", SR.ID2030)] + [InlineData("/tmp/file.xml", SR.ID2030)] + [InlineData("C:\\tmp\\file.xml", SR.ID2030)] + [InlineData("http://www.fabrikam.com/path#param=value", SR.ID2031)] + public async Task ValidateAuthorizationRequest_InvalidRedirectUriCausesAnError(string address, string message) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = address, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(string.Format(SR.GetResourceString(message), Parameters.RedirectUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(message), response.ErrorUri); + } - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = null, - Scope = Scopes.OpenId - }); + [Fact] + public async Task ValidateAuthorizationRequest_MissingResponseTypeCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = null, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.ResponseType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.ResponseType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } - - [Theory] - [InlineData("code id_token", ResponseModes.Query)] - [InlineData("code id_token token", ResponseModes.Query)] - [InlineData("code token", ResponseModes.Query)] - [InlineData("id_token", ResponseModes.Query)] - [InlineData("id_token token", ResponseModes.Query)] - [InlineData("token", ResponseModes.Query)] - public async Task ValidateAuthorizationRequest_UnsafeResponseModeCausesAnError(string type, string mode) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseMode = mode, - ResponseType = type, - Scope = Scopes.OpenId - }); + [Theory] + [InlineData("code id_token", ResponseModes.Query)] + [InlineData("code id_token token", ResponseModes.Query)] + [InlineData("code token", ResponseModes.Query)] + [InlineData("id_token", ResponseModes.Query)] + [InlineData("id_token token", ResponseModes.Query)] + [InlineData("token", ResponseModes.Query)] + public async Task ValidateAuthorizationRequest_UnsafeResponseModeCausesAnError(string type, string mode) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseMode = mode, + ResponseType = type, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2033(Parameters.ResponseType, Parameters.ResponseMode), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2033), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2033(Parameters.ResponseType, Parameters.ResponseMode), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2033), response.ErrorUri); - } - - [Theory] - [InlineData("code id_token")] - [InlineData("code id_token token")] - [InlineData("code token")] - [InlineData("id_token")] - [InlineData("id_token token")] - [InlineData("token")] - public async Task ValidateAuthorizationRequest_MissingNonceCausesAnErrorForOpenIdRequests(string type) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + [Theory] + [InlineData("code id_token")] + [InlineData("code id_token token")] + [InlineData("code token")] + [InlineData("id_token")] + [InlineData("id_token token")] + [InlineData("token")] + public async Task ValidateAuthorizationRequest_MissingNonceCausesAnErrorForOpenIdRequests(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.Nonce), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.Nonce), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } - - [Theory] - [InlineData("code id_token")] - [InlineData("code id_token token")] - [InlineData("id_token")] - [InlineData("id_token token")] - public async Task ValidateAuthorizationRequest_MissingOpenIdScopeCausesAnErrorForOpenIdRequests(string type) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type - }); + [Theory] + [InlineData("code id_token")] + [InlineData("code id_token token")] + [InlineData("id_token")] + [InlineData("id_token token")] + public async Task ValidateAuthorizationRequest_MissingOpenIdScopeCausesAnErrorForOpenIdRequests(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2034(Scopes.OpenId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2034), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2034(Scopes.OpenId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2034), response.ErrorUri); - } + [Theory] + [InlineData("none consent")] + [InlineData("none login")] + [InlineData("none select_account")] + public async Task ValidateAuthorizationRequest_InvalidPromptCausesAnError(string prompt) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + Prompt = prompt, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = "code id_token token", + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.Prompt), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + } - [Theory] - [InlineData("none consent")] - [InlineData("none login")] - [InlineData("none select_account")] - public async Task ValidateAuthorizationRequest_InvalidPromptCausesAnError(string prompt) + [Theory] + [InlineData("none")] + [InlineData("consent")] + [InlineData("login")] + [InlineData("select_account")] + [InlineData("consent login")] + [InlineData("consent select_account")] + [InlineData("login select_account")] + [InlineData("consent login select_account")] + public async Task ValidateAuthorizationRequest_ValidPromptDoesNotCauseAnError(string prompt) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + options.EnableDegradedMode(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - Prompt = prompt, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = "code id_token token", - Scope = Scopes.OpenId - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.Prompt), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - } - - [Theory] - [InlineData("none")] - [InlineData("consent")] - [InlineData("login")] - [InlineData("select_account")] - [InlineData("consent login")] - [InlineData("consent select_account")] - [InlineData("login select_account")] - [InlineData("consent login select_account")] - public async Task ValidateAuthorizationRequest_ValidPromptDoesNotCauseAnError(string prompt) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + Prompt = prompt, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = "code id_token token", + Scope = Scopes.OpenId + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); + Assert.NotNull(response.Code); + Assert.NotNull(response.IdToken); + } - return default; - })); - }); + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenPkceIsRequiredAndCodeChallengeIsMissing() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.RequireProofKeyForCodeExchange(); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - Prompt = prompt, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = "code id_token token", - Scope = Scopes.OpenId - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = null, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.AccessToken); - Assert.NotNull(response.Code); - Assert.NotNull(response.IdToken); - } + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.CodeChallenge), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenPkceIsRequiredAndCodeChallengeIsMissing() + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsValidateWhenPkceIsNotRequiredAndCodeChallengeIsMissing() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RequireProofKeyForCodeExchange(); - }); + options.EnableDegradedMode(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = null, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + return default; + })); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.CodeChallenge), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsValidateWhenPkceIsNotRequiredAndCodeChallengeIsMissing() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + CodeChallenge = null, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); - }); + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.Code); + } - await using var client = await server.CreateClientAsync(); + [Theory] + [InlineData("id_token")] + [InlineData("id_token token")] + [InlineData("token")] + public async Task ValidateAuthorizationRequest_MissingCodeResponseTypeCausesAnErrorWhenCodeChallengeIsUsed(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", + CodeChallengeMethod = CodeChallengeMethods.Sha256, + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2040(Parameters.CodeChallenge, Parameters.CodeChallengeMethod, ResponseTypes.Code), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2040), response.ErrorUri); + } - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = null, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + [Fact] + public async Task ValidateAuthorizationRequest_MissingCodeChallengeCausesAnErrorWhenCodeChallengeMethodIsSpecified() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallengeMethod = CodeChallengeMethods.Sha256, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2037(Parameters.CodeChallengeMethod, Parameters.CodeChallenge), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2037), response.ErrorUri); + } - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.Code); - } - - [Theory] - [InlineData("id_token")] - [InlineData("id_token token")] - [InlineData("token")] - public async Task ValidateAuthorizationRequest_MissingCodeResponseTypeCausesAnErrorWhenCodeChallengeIsUsed(string type) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - CodeChallengeMethod = CodeChallengeMethods.Sha256, - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + [Fact] + public async Task ValidateAuthorizationRequest_InvalidCodeChallengeMethodCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", + CodeChallengeMethod = "invalid_code_challenge_method", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2032(Parameters.CodeChallengeMethod), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2040(Parameters.CodeChallenge, Parameters.CodeChallengeMethod, ResponseTypes.Code), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2040), response.ErrorUri); - } + [Fact] + public async Task ValidateAuthorizationRequest_UnknownResponseTypeParameterIsRejected() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = "unknown_response_type" + }); + + // Assert + Assert.Equal(Errors.UnsupportedResponseType, response.Error); + Assert.Equal(SR.FormatID2032(Parameters.ResponseType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); + } - [Fact] - public async Task ValidateAuthorizationRequest_MissingCodeChallengeCausesAnErrorWhenCodeChallengeMethodIsSpecified() + [Theory] + [InlineData(GrantTypes.AuthorizationCode, "code")] + [InlineData(GrantTypes.AuthorizationCode, "code id_token")] + [InlineData(GrantTypes.AuthorizationCode, "code id_token token")] + [InlineData(GrantTypes.AuthorizationCode, "code token")] + [InlineData(GrantTypes.Implicit, "code id_token")] + [InlineData(GrantTypes.Implicit, "code id_token token")] + [InlineData(GrantTypes.Implicit, "code token")] + [InlineData(GrantTypes.Implicit, "id_token")] + [InlineData(GrantTypes.Implicit, "id_token token")] + [InlineData(GrantTypes.Implicit, "token")] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenGrantTypeIsDisabled(string flow, string type) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + options.Configure(options => options.GrantTypes.Remove(flow)); + options.Configure(options => options.ResponseTypes.Clear()); + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallengeMethod = CodeChallengeMethods.Sha256, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2037(Parameters.CodeChallengeMethod, Parameters.CodeChallenge), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2037), response.ErrorUri); - } - - [Fact] - public async Task ValidateAuthorizationRequest_InvalidCodeChallengeMethodCausesAnError() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - CodeChallengeMethod = "invalid_code_challenge_method", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2032(Parameters.CodeChallengeMethod), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); - } + // Assert + Assert.Equal(Errors.UnsupportedResponseType, response.Error); + Assert.Equal(SR.FormatID2032(Parameters.ResponseType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); + } - [Fact] - public async Task ValidateAuthorizationRequest_UnknownResponseTypeParameterIsRejected() + [Theory] + [InlineData("code")] + [InlineData("code id_token")] + [InlineData("code id_token token")] + [InlineData("code token")] + [InlineData("id_token")] + [InlineData("id_token token")] + [InlineData("none")] + [InlineData("token")] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenResponseTypeIsDisabled(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + options.Configure(options => options.ResponseTypes.Remove(type)); + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = "unknown_response_type" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.UnsupportedResponseType, response.Error); - Assert.Equal(SR.FormatID2032(Parameters.ResponseType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); - } - - [Theory] - [InlineData(GrantTypes.AuthorizationCode, "code")] - [InlineData(GrantTypes.AuthorizationCode, "code id_token")] - [InlineData(GrantTypes.AuthorizationCode, "code id_token token")] - [InlineData(GrantTypes.AuthorizationCode, "code token")] - [InlineData(GrantTypes.Implicit, "code id_token")] - [InlineData(GrantTypes.Implicit, "code id_token token")] - [InlineData(GrantTypes.Implicit, "code token")] - [InlineData(GrantTypes.Implicit, "id_token")] - [InlineData(GrantTypes.Implicit, "id_token token")] - [InlineData(GrantTypes.Implicit, "token")] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenGrantTypeIsDisabled(string flow, string type) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Configure(options => options.GrantTypes.Remove(flow)); - options.Configure(options => options.ResponseTypes.Clear()); - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Equal(Errors.UnsupportedResponseType, response.Error); + Assert.Equal(SR.FormatID2032(Parameters.ResponseType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); + } - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + [Fact] + public async Task ValidateAuthorizationRequest_UnsupportedResponseModeCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseMode = "unsupported_response_mode", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2032(Parameters.ResponseMode), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.UnsupportedResponseType, response.Error); - Assert.Equal(SR.FormatID2032(Parameters.ResponseType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); - } - - [Theory] - [InlineData("code")] - [InlineData("code id_token")] - [InlineData("code id_token token")] - [InlineData("code token")] - [InlineData("id_token")] - [InlineData("id_token token")] - [InlineData("none")] - [InlineData("token")] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenResponseTypeIsDisabled(string type) - { - // Arrange - await using var server = await CreateServerAsync(options => + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenUnregisteredScopeIsSpecified() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.Configure(options => options.ResponseTypes.Remove(type)); - }); - - await using var client = await server.CreateClientAsync(); + var application = new OpenIddictApplication(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Assert - Assert.Equal(Errors.UnsupportedResponseType, response.Error); - Assert.Equal(SR.FormatID2032(Parameters.ResponseType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); - } + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - [Fact] - public async Task ValidateAuthorizationRequest_UnsupportedResponseModeCausesAnError() - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + options.Services.AddSingleton(CreateScopeManager(mock => { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseMode = "unsupported_response_mode", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + mock.Setup(manager => manager.FindByNamesAsync( + It.Is>(scopes => scopes.Length == 1 && scopes[0] == "unregistered_scope"), + It.IsAny())) + .Returns(AsyncEnumerable.Empty()); + })); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2032(Parameters.ResponseMode), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenUnregisteredScopeIsSpecified() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(CreateApplicationManager(mock => - { - var application = new OpenIddictApplication(); - - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = "unregistered_scope" + }); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + // Assert + Assert.Equal(Errors.InvalidScope, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + } - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - })); + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenScopeRegisteredInOptionsIsSpecified() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.RegisterScopes("registered_scope"); - options.Services.AddSingleton(CreateScopeManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - mock.Setup(manager => manager.FindByNamesAsync( - It.Is>(scopes => scopes.Length == 1 && scopes[0] == "unregistered_scope"), - It.IsAny())) - .Returns(AsyncEnumerable.Empty()); - })); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = "unregistered_scope" - }); + return default; + })); + }); - // Assert - Assert.Equal(Errors.InvalidScope, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenScopeRegisteredInOptionsIsSpecified() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RegisterScopes("registered_scope"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); - }); + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Token, + Scope = "registered_scope" + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); + } - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Token, - Scope = "registered_scope" - }); + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenRegisteredScopeIsSpecified() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + var scope = new OpenIddictScope(); - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.AccessToken); - } + options.RegisterScopes("scope_registered_in_options"); + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenRegisteredScopeIsSpecified() - { - // Arrange - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateApplicationManager(mock => { - var scope = new OpenIddictScope(); + var application = new OpenIddictApplication(); - options.RegisterScopes("scope_registered_in_options"); - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - options.Services.AddSingleton(CreateApplicationManager(mock => - { - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + options.Services.AddSingleton(CreateScopeManager(mock => + { + mock.Setup(manager => manager.FindByNamesAsync( + It.Is>(scopes => scopes.Length == 1 && scopes[0] == "scope_registered_in_database"), + It.IsAny())) + .Returns(new[] { scope }.ToAsyncEnumerable()); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - })); + mock.Setup(manager => manager.GetNameAsync(scope, It.IsAny())) + .ReturnsAsync("scope_registered_in_database"); + })); - options.Services.AddSingleton(CreateScopeManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - mock.Setup(manager => manager.FindByNamesAsync( - It.Is>(scopes => scopes.Length == 1 && scopes[0] == "scope_registered_in_database"), - It.IsAny())) - .Returns(new[] { scope }.ToAsyncEnumerable()); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.GetNameAsync(scope, It.IsAny())) - .ReturnsAsync("scope_registered_in_database"); + return default; })); + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + await using var client = await server.CreateClientAsync(); - return default; - })); - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Token, + Scope = "scope_registered_in_database scope_registered_in_options" + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); + } - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Token, - Scope = "scope_registered_in_database scope_registered_in_options" - }); + [Fact] + public async Task ValidateAuthorizationRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenFlowIsDisabled() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.Configure(options => options.GrantTypes.Remove(GrantTypes.RefreshToken)); + }); - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenFlowIsDisabled() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Configure(options => options.GrantTypes.Remove(GrantTypes.RefreshToken)); - }); + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OfflineAccess + }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OfflineAccess - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2035(Scopes.OfflineAccess), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2035), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2035(Scopes.OfflineAccess), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2035), response.ErrorUri); - } + [Fact] + public async Task ValidateAuthorizationRequest_UnknownResponseModeParameterIsRejected() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseMode = "unknown_response_mode", + ResponseType = ResponseTypes.Code + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2032(Parameters.ResponseMode), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); + } - [Fact] - public async Task ValidateAuthorizationRequest_UnknownResponseModeParameterIsRejected() - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCodeChallengeMethodIsMissing() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", + CodeChallengeMethod = null, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.CodeChallengeMethod), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseMode = "unknown_response_mode", - ResponseType = ResponseTypes.Code - }); + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCodeChallengeMethodIsNotEnabled() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.Services.PostConfigure(options => + options.CodeChallengeMethods.Clear()); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", + CodeChallengeMethod = CodeChallengeMethods.Sha256, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2032(Parameters.CodeChallengeMethod), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2032(Parameters.ResponseMode), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); - } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenPlainCodeChallengeMethodIsNotExplicitlyEnabled() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", + CodeChallengeMethod = CodeChallengeMethods.Plain, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2032(Parameters.CodeChallengeMethod), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); + } - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCodeChallengeMethodIsMissing() + [Theory] + [InlineData(CodeChallengeMethods.Plain)] + [InlineData(CodeChallengeMethods.Sha256)] + [InlineData("custom_code_challenge_method")] + public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeChallengeMethodIsRegistered(string method) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + options.EnableDegradedMode(); + options.Configure(options => options.CodeChallengeMethods.Clear()); + options.Configure(options => options.CodeChallengeMethods.Add(method)); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - CodeChallengeMethod = null, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + })); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.CodeChallengeMethod), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCodeChallengeMethodIsNotEnabled() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.Services.PostConfigure(options => - options.CodeChallengeMethods.Clear()); - }); + ClientId = "Fabrikam", + CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", + CodeChallengeMethod = method, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.Code); + } - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - CodeChallengeMethod = CodeChallengeMethods.Sha256, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + [Theory] + [InlineData("code id_token token")] + [InlineData("code token")] + public async Task ValidateAuthorizationRequest_PkceRequestWithForbiddenResponseTypeIsRejected(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", + CodeChallengeMethod = CodeChallengeMethods.Sha256, + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2041(Parameters.ResponseType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2041), response.ErrorUri); + } + + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsMissing() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = null, + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.RedirectUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2032(Parameters.CodeChallengeMethod), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); - } + [Fact] + public async Task ValidateAuthorizationRequest_MissingRedirectUriCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenPlainCodeChallengeMethodIsNotExplicitlyEnabled() + // Act and assert + var exception = await Assert.ThrowsAsync(delegate { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + return client.PostAsync("/connect/authorize", new OpenIddictRequest { ClientId = "Fabrikam", - CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - CodeChallengeMethod = CodeChallengeMethods.Plain, - RedirectUri = "http://www.fabrikam.com/path", + RedirectUri = null, ResponseType = ResponseTypes.Code }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2032(Parameters.CodeChallengeMethod), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); - } + // Assert + Assert.Equal(SR.GetResourceString(SR.ID0028), exception.Message); + } - [Theory] - [InlineData(CodeChallengeMethods.Plain)] - [InlineData(CodeChallengeMethods.Sha256)] - [InlineData("custom_code_challenge_method")] - public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeChallengeMethodIsRegistered(string method) + [Fact] + public async Task ValidateAuthorizationRequest_InvalidRedirectUriCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.Configure(options => options.CodeChallengeMethods.Clear()); - options.Configure(options => options.CodeChallengeMethods.Add(method)); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SetRedirectUri("http://www.contoso.com/path"); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/connect/authorize", new OpenIddictRequest { ClientId = "Fabrikam", - CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - CodeChallengeMethod = method, RedirectUri = "http://www.fabrikam.com/path", ResponseType = ResponseTypes.Code }); + }); - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.Code); - } + // Assert + Assert.Equal(SR.GetResourceString(SR.ID0101), exception.Message); + } - [Theory] - [InlineData("code id_token token")] - [InlineData("code token")] - public async Task ValidateAuthorizationRequest_PkceRequestWithForbiddenResponseTypeIsRejected(string type) + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenClientCannotBeFound() + { + // Arrange + var manager = CreateApplicationManager(mock => { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(value: null); + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - CodeChallengeMethod = CodeChallengeMethods.Sha256, - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2041(Parameters.ResponseType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2041), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsMissing() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = null, - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.RedirectUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + } - [Fact] - public async Task ValidateAuthorizationRequest_MissingRedirectUriCausesAnException() + [Theory] + [InlineData("code id_token token")] + [InlineData("code token")] + [InlineData("id_token token")] + [InlineData("token")] + public async Task ValidateAuthorizationRequest_AnAccessTokenIsNotReturnedWhenClientIsConfidential(string type) + { + // Arrange + var application = new OpenIddictApplication(); + + var manager = CreateApplicationManager(mock => { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = null, - ResponseType = ResponseTypes.Code - }); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); - // Assert - Assert.Equal(SR.GetResourceString(SR.ID0028), exception.Message); - } + mock.Setup(manager => manager.GetPermissionsAsync(application, It.IsAny())) + .ReturnsAsync(ImmutableArray.Create()); + }); - [Fact] - public async Task ValidateAuthorizationRequest_InvalidRedirectUriCausesAnException() + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.Services.AddSingleton(manager); + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SetRedirectUri("http://www.contoso.com/path"); + await using var client = await server.CreateClientAsync(); - return default; - })); - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.FormatID2043(Parameters.ResponseType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2043), response.ErrorUri); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); - }); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny()), Times.Once()); + } - // Assert - Assert.Equal(SR.GetResourceString(SR.ID0101), exception.Message); - } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenClientCannotBeFound() + var manager = CreateApplicationManager(mock => { - // Arrange - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(value: null); - }); - - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - } + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Authorization, It.IsAny())) + .ReturnsAsync(false); + }); - [Theory] - [InlineData("code id_token token")] - [InlineData("code token")] - [InlineData("id_token token")] - [InlineData("token")] - public async Task ValidateAuthorizationRequest_AnAccessTokenIsNotReturnedWhenClientIsConfidential(string type) + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + options.Services.AddSingleton(manager); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) - .ReturnsAsync(true); + options.Configure(options => options.IgnoreEndpointPermissions = false); + }); - mock.Setup(manager => manager.GetPermissionsAsync(application, It.IsAny())) - .ReturnsAsync(ImmutableArray.Create()); - }); + await using var client = await server.CreateClientAsync(); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2046), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2046), response.ErrorUri); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); - - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.FormatID2043(Parameters.ResponseType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2043), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Authorization, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny()), Times.Once()); - } + [Theory] + [InlineData( + "code", + new[] { Permissions.GrantTypes.AuthorizationCode }, + SR.ID2047)] + [InlineData( + "code id_token", + new[] { Permissions.GrantTypes.AuthorizationCode, Permissions.GrantTypes.Implicit }, + SR.ID2049)] + [InlineData( + "code id_token token", + new[] { Permissions.GrantTypes.AuthorizationCode, Permissions.GrantTypes.Implicit }, + SR.ID2049)] + [InlineData( + "code token", + new[] { Permissions.GrantTypes.AuthorizationCode, Permissions.GrantTypes.Implicit }, + SR.ID2049)] + [InlineData( + "id_token", + new[] { Permissions.GrantTypes.Implicit }, + SR.ID2048)] + [InlineData( + "id_token token", + new[] { Permissions.GrantTypes.Implicit }, + SR.ID2048)] + [InlineData( + "token", + new[] { Permissions.GrantTypes.Implicit }, + SR.ID2048)] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted( + string type, string[] permissions, string description) + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); - - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Authorization, It.IsAny())) - .ReturnsAsync(false); - }); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - await using var server = await CreateServerAsync(options => + foreach (var permission in permissions) { - options.Services.AddSingleton(manager); - - options.Configure(options => options.IgnoreEndpointPermissions = false); - }); - - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasPermissionAsync(application, permission, It.IsAny())) + .ReturnsAsync(false); + } + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2046), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2046), response.ErrorUri); - - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Authorization, It.IsAny()), Times.Once()); - } - - [Theory] - [InlineData( - "code", - new[] { Permissions.GrantTypes.AuthorizationCode }, - SR.ID2047)] - [InlineData( - "code id_token", - new[] { Permissions.GrantTypes.AuthorizationCode, Permissions.GrantTypes.Implicit }, - SR.ID2049)] - [InlineData( - "code id_token token", - new[] { Permissions.GrantTypes.AuthorizationCode, Permissions.GrantTypes.Implicit }, - SR.ID2049)] - [InlineData( - "code token", - new[] { Permissions.GrantTypes.AuthorizationCode, Permissions.GrantTypes.Implicit }, - SR.ID2049)] - [InlineData( - "id_token", - new[] { Permissions.GrantTypes.Implicit }, - SR.ID2048)] - [InlineData( - "id_token token", - new[] { Permissions.GrantTypes.Implicit }, - SR.ID2048)] - [InlineData( - "token", - new[] { Permissions.GrantTypes.Implicit }, - SR.ID2048)] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted( - string type, string[] permissions, string description) - { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + options.Configure(options => options.IgnoreGrantTypePermissions = false); + }); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + await using var client = await server.CreateClientAsync(); - foreach (var permission in permissions) - { - mock.Setup(manager => manager.HasPermissionAsync(application, permission, It.IsAny())) - .ReturnsAsync(false); - } - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.GetResourceString(description), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(description), response.ErrorUri); - options.Configure(options => options.IgnoreGrantTypePermissions = false); - }); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, permissions[0], It.IsAny()), Times.Once()); + } - await using var client = await server.CreateClientAsync(); + [Theory] + [InlineData("code")] + [InlineData("code id_token")] + [InlineData("code id_token token")] + [InlineData("code token")] + [InlineData("id_token")] + [InlineData("id_token token")] + [InlineData("none")] + [InlineData("token")] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenResponseTypePermissionIsNotGranted(string type) + { + // Arrange + var application = new OpenIddictApplication(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.GetResourceString(description), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(description), response.ErrorUri); - - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, permissions[0], It.IsAny()), Times.Once()); - } - - [Theory] - [InlineData("code")] - [InlineData("code id_token")] - [InlineData("code id_token token")] - [InlineData("code token")] - [InlineData("id_token")] - [InlineData("id_token token")] - [InlineData("none")] - [InlineData("token")] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenResponseTypePermissionIsNotGranted(string type) - { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetPermissionsAsync(application, It.IsAny())) + .ReturnsAsync(ImmutableArray.Create()); + }); - mock.Setup(manager => manager.GetPermissionsAsync(application, It.IsAny())) - .ReturnsAsync(ImmutableArray.Create()); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + options.Configure(options => options.IgnoreResponseTypePermissions = false); + }); - options.Configure(options => options.IgnoreResponseTypePermissions = false); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.FormatID2043(Parameters.ResponseType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2043), response.ErrorUri); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.FormatID2043(Parameters.ResponseType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2043), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.GetPermissionsAsync(application, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetPermissionsAsync(application, It.IsAny()), Times.Once()); - } + [Theory] + [InlineData("code id_token token")] + [InlineData("code token")] + [InlineData("id_token token")] + [InlineData("token")] + public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenExplicitPermissionIsGranted(string type) + { + // Arrange + var application = new OpenIddictApplication(); - [Theory] - [InlineData("code id_token token")] - [InlineData("code token")] - [InlineData("id_token token")] - [InlineData("token")] - public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenExplicitPermissionIsGranted(string type) + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.GetPermissionsAsync(application, It.IsAny())) + .ReturnsAsync(ImmutableArray.Create("rst:" + type)); - mock.Setup(manager => manager.GetPermissionsAsync(application, It.IsAny())) - .ReturnsAsync(ImmutableArray.Create("rst:" + type)); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); + }); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + options.DisableAuthorizationStorage(); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); - options.DisableAuthorizationStorage(); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + options.Configure(options => options.IgnoreResponseTypePermissions = false); - options.Configure(options => options.IgnoreResponseTypePermissions = false); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + return default; + })); + }); - return default; - })); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.AccessToken); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.GetPermissionsAsync(application, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.GetPermissionsAsync(application, It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateAuthorizationRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenPermissionIsNotGranted() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.AuthorizationCode, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.AuthorizationCode, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.RefreshToken, It.IsAny())) - .ReturnsAsync(false); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + options.Configure(options => options.IgnoreGrantTypePermissions = false); + }); - options.Configure(options => options.IgnoreGrantTypePermissions = false); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OfflineAccess + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OfflineAccess - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2065(Scopes.OfflineAccess), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2065), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2065(Scopes.OfflineAccess), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2065), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.RefreshToken, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.RefreshToken, It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsInvalid() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsInvalid() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(false); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2043(Parameters.RedirectUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2043), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2043(Parameters.RedirectUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2043), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenScopePermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenScopePermissionIsNotGranted() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Email, It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Email, It.IsAny())) - .ReturnsAsync(false); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + options.RegisterScopes(Scopes.Email, Scopes.Profile); + options.Configure(options => options.IgnoreScopePermissions = false); + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - options.RegisterScopes(Scopes.Email, Scopes.Profile); - options.Configure(options => options.IgnoreScopePermissions = false); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = "openid offline_access profile email" + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = "openid offline_access profile email" - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2051), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2051), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2051), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2051), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.OpenId, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.OfflineAccess, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.OpenId, It.IsAny()), Times.Never()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.OfflineAccess, It.IsAny()), Times.Never()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCodeChallengeIsMissingWithPkceFeatureEnforced() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCodeChallengeIsMissingWithPkceFeatureEnforced() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) - .ReturnsAsync(true); - }); + mock.Setup(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) + .ReturnsAsync(true); + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = null, - CodeChallengeMethod = null, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = null, + CodeChallengeMethod = null, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2054(Parameters.CodeChallenge), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2054(Parameters.CodeChallenge), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeChallengeIsMissingWithPkceFeatureNotEnforced() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeChallengeIsMissingWithPkceFeatureNotEnforced() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) - .ReturnsAsync(false); - }); + await using var server = await CreateServerAsync(options => + { + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableAuthorizationStorage(); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); - await using var server = await CreateServerAsync(options => - { - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableAuthorizationStorage(); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + options.Services.AddSingleton(manager); - options.Services.AddSingleton(manager); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + return default; + })); + }); - return default; - })); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = null, + CodeChallengeMethod = null, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = null, - CodeChallengeMethod = null, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + // Assert + Assert.NotNull(response.Code); - // Assert - Assert.NotNull(response.Code); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeChallengeIsPresentWithPkceFeatureEnforced() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeChallengeIsPresentWithPkceFeatureEnforced() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) + .ReturnsAsync(true); + }); - mock.Setup(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) - .ReturnsAsync(true); - }); + await using var server = await CreateServerAsync(options => + { + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableAuthorizationStorage(); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); - await using var server = await CreateServerAsync(options => - { - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableAuthorizationStorage(); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + options.Services.AddSingleton(manager); - options.Services.AddSingleton(manager); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + return default; + })); + }); - return default; - })); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", + CodeChallengeMethod = CodeChallengeMethods.Sha256, + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - CodeChallengeMethod = CodeChallengeMethods.Sha256, - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + // Assert + Assert.NotNull(response.Code); - // Assert - Assert.NotNull(response.Code); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never()); - } + [Fact] + public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeIsNotRequestedWithPkceFeatureEnforced() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeIsNotRequestedWithPkceFeatureEnforced() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) + .ReturnsAsync(true); + }); - mock.Setup(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) - .ReturnsAsync(true); - }); + await using var server = await CreateServerAsync(options => + { + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableAuthorizationStorage(); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); - await using var server = await CreateServerAsync(options => - { - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableAuthorizationStorage(); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + options.Services.AddSingleton(manager); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.Services.AddSingleton(manager); + return default; + })); + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + await using var client = await server.CreateClientAsync(); - return default; - })); - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Token + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Null(response.Code); + Assert.NotNull(response.AccessToken); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Token - }); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never()); + } - // Assert - Assert.Null(response.Code); - Assert.NotNull(response.AccessToken); - - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never()); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateAuthorizationRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateAuthorizationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ValidateAuthorizationRequest_AllowsHandlingResponse() + [Fact] + public async Task ValidateAuthorizationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ValidateAuthorizationRequest_AllowsSkippingHandler() + [Fact] + public async Task ValidateAuthorizationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleAuthorizationRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleAuthorizationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task HandleAuthorizationRequest_AllowsHandlingResponse() + [Fact] + public async Task HandleAuthorizationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task HandleAuthorizationRequest_AllowsSkippingHandler() + [Fact] + public async Task HandleAuthorizationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task HandleAuthorizationRequest_ResponseContainsCustomParameters() + [Fact] + public async Task HandleAuthorizationRequest_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - context.Parameters["custom_parameter"] = "custom_value"; - context.Parameters["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - await using var client = await server.CreateClientAsync(); + context.Parameters["custom_parameter"] = "custom_value"; + context.Parameters["parameter_with_multiple_values"] = new[] + { + "custom_value_1", + "custom_value_2" + }; - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Token - }); + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Token + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); + } - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.AccessToken); - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } - - [Theory] - [InlineData("code", ResponseModes.Query)] - [InlineData("code id_token", ResponseModes.Fragment)] - [InlineData("code id_token token", ResponseModes.Fragment)] - [InlineData("code token", ResponseModes.Fragment)] - [InlineData("id_token", ResponseModes.Fragment)] - [InlineData("id_token token", ResponseModes.Fragment)] - [InlineData("token", ResponseModes.Fragment)] - public async Task ApplyAuthorizationResponse_ResponseModeIsAutomaticallyInferred(string type, string mode) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + [Theory] + [InlineData("code", ResponseModes.Query)] + [InlineData("code id_token", ResponseModes.Fragment)] + [InlineData("code id_token token", ResponseModes.Fragment)] + [InlineData("code token", ResponseModes.Fragment)] + [InlineData("id_token", ResponseModes.Fragment)] + [InlineData("id_token token", ResponseModes.Fragment)] + [InlineData("token", ResponseModes.Fragment)] + public async Task ApplyAuthorizationResponse_ResponseModeIsAutomaticallyInferred(string type, string mode) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Response["inferred_response_mode"] = context.ResponseMode; + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["inferred_response_mode"] = context.ResponseMode; - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); - // Assert - Assert.Equal(mode, (string?) response["inferred_response_mode"]); - } + // Assert + Assert.Equal(mode, (string?) response["inferred_response_mode"]); + } - [Fact] - public async Task ApplyAuthorizationResponse_AllowsHandlingResponse() + [Fact] + public async Task ApplyAuthorizationResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyAuthorizationResponse_ResponseContainsCustomParameters() + [Fact] + public async Task ApplyAuthorizationResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; + context.Response["parameter_with_multiple_values"] = new[] { - context.Response["custom_parameter"] = "custom_value"; - context.Response["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); + "custom_value_1", + "custom_value_2" + }; - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + return default; + })); + }); - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ApplyAuthorizationResponse_ThrowsAnExceptionWhenRequestIsMissing() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Note: an exception is only thrown if the request was not properly extracted - // AND if the developer decided to override the error to return a custom response. - // To emulate this behavior, the error property is manually set to null. + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Response.Error = null; + [Fact] + public async Task ApplyAuthorizationResponse_ThrowsAnExceptionWhenRequestIsMissing() + { + // Note: an exception is only thrown if the request was not properly extracted + // AND if the developer decided to override the error to return a custom response. + // To emulate this behavior, the error property is manually set to null. - return default; - })); - }); + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response.Error = null; - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.SendAsync(HttpMethod.Put, "/connect/authorize", new OpenIddictRequest()); - }); + return default; + })); + }); - Assert.Equal(SR.GetResourceString(SR.ID0030), exception.Message); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ApplyAuthorizationResponse_DoesNotSetStateWhenUserIsNotRedirected() + // Act and assert + var exception = await Assert.ThrowsAsync(delegate { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + return client.SendAsync(HttpMethod.Put, "/connect/authorize", new OpenIddictRequest()); + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + Assert.Equal(SR.GetResourceString(SR.ID0030), exception.Message); + } - return default; - })); + [Fact] + public async Task ApplyAuthorizationResponse_DoesNotSetStateWhenUserIsNotRedirected() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); - }); + return default; + })); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - ResponseType = ResponseTypes.Code, - State = "af0ifjsldkj" - }); + return default; + })); + }); - // Assert - Assert.Null(response.State); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ApplyAuthorizationResponse_FlowsStateWhenRedirectUriIsUsed() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + ResponseType = ResponseTypes.Code, + State = "af0ifjsldkj" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.Null(response.State); + } - return default; - })); - }); + [Fact] + public async Task ApplyAuthorizationResponse_FlowsStateWhenRedirectUriIsUsed() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - State = "af0ifjsldkj" - }); + return default; + })); + }); - // Assert - Assert.Equal("af0ifjsldkj", response.State); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ApplyAuthorizationResponse_DoesNotOverrideStateSetByApplicationCode() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + State = "af0ifjsldkj" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.Equal("af0ifjsldkj", response.State); + } - return default; - })); + [Fact] + public async Task ApplyAuthorizationResponse_DoesNotOverrideStateSetByApplicationCode() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Response.State = "custom_state"; + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); - }); + return default; + })); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response.State = "custom_state"; - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - State = "af0ifjsldkj" - }); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + State = "af0ifjsldkj" + }); - // Assert - Assert.Equal("custom_state", response.State); - } + // Assert + Assert.Equal("custom_state", response.State); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs index a689657e..b9015b99 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs @@ -20,1441 +20,1440 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Get))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractDeviceRequest_UnexpectedMethodReturnsAnError(string method) { - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Get))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractDeviceRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/connect/device", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractDeviceRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + // Act + var response = await client.SendAsync(method, "/connect/device", new OpenIddictRequest()); - return default; - })); - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractDeviceRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest()); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest()); - [Fact] - public async Task ExtractDeviceRequest_AllowsHandlingResponse() + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } + + [Fact] + public async Task ExtractDeviceRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractDeviceRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractDeviceRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest()); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest()); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + [Fact] + public async Task ValidateDeviceRequest_MissingClientIdCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateDeviceRequest_MissingClientIdCausesAnError() + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + ClientId = null + }); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = null - }); + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + [Fact] + public async Task ValidateDeviceRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenFlowIsDisabled() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateDeviceRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenFlowIsDisabled() + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); - - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.Services.AddSingleton(CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - })); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - options.Configure(options => options.GrantTypes.Remove(GrantTypes.RefreshToken)); - }); + options.Configure(options => options.GrantTypes.Remove(GrantTypes.RefreshToken)); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam", - Scope = Scopes.OfflineAccess - }); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam", + Scope = Scopes.OfflineAccess + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2035(Scopes.OfflineAccess), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2035), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2035(Scopes.OfflineAccess), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2035), response.ErrorUri); - } + [Fact] + public async Task ValidateDeviceRequest_RequestIsRejectedWhenUnregisteredScopeIsSpecified() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateDeviceRequest_RequestIsRejectedWhenUnregisteredScopeIsSpecified() + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); - - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.Services.AddSingleton(CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - })); - - options.Services.AddSingleton(CreateScopeManager(mock => - { - mock.Setup(manager => manager.FindByNamesAsync( - It.Is>(scopes => scopes.Length == 1 && scopes[0] == "unregistered_scope"), - It.IsAny())) - .Returns(AsyncEnumerable.Empty()); - })); - }); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest + options.Services.AddSingleton(CreateScopeManager(mock => { - ClientId = "Fabrikam", - Scope = "unregistered_scope" - }); + mock.Setup(manager => manager.FindByNamesAsync( + It.Is>(scopes => scopes.Length == 1 && scopes[0] == "unregistered_scope"), + It.IsAny())) + .Returns(AsyncEnumerable.Empty()); + })); + }); - // Assert - Assert.Equal(Errors.InvalidScope, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateDeviceRequest_RequestIsValidatedWhenScopeRegisteredInOptionsIsSpecified() + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); + ClientId = "Fabrikam", + Scope = "unregistered_scope" + }); + + // Assert + Assert.Equal(Errors.InvalidScope, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + } + + [Fact] + public async Task ValidateDeviceRequest_RequestIsValidatedWhenScopeRegisteredInOptionsIsSpecified() + { + // Arrange + var application = new OpenIddictApplication(); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.Services.AddSingleton(CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - })); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - options.RegisterScopes("registered_scope"); - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableAuthorizationStorage(); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + options.RegisterScopes("registered_scope"); + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableAuthorizationStorage(); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam", - Scope = "registered_scope" - }); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam", + Scope = "registered_scope" + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.DeviceCode); + Assert.NotNull(response.UserCode); + } - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.DeviceCode); - Assert.NotNull(response.UserCode); - } + [Fact] + public async Task ValidateDeviceRequest_RequestIsValidatedWhenRegisteredScopeIsSpecified() + { + // Arrange + var application = new OpenIddictApplication(); + var scope = new OpenIddictScope(); - [Fact] - public async Task ValidateDeviceRequest_RequestIsValidatedWhenRegisteredScopeIsSpecified() + var manager = CreateScopeManager(mock => { - // Arrange - var application = new OpenIddictApplication(); - var scope = new OpenIddictScope(); + mock.Setup(manager => manager.FindByNamesAsync( + It.Is>(scopes => scopes.Length == 1 && scopes[0] == "scope_registered_in_database"), + It.IsAny())) + .Returns(new[] { scope }.ToAsyncEnumerable()); - var manager = CreateScopeManager(mock => - { - mock.Setup(manager => manager.FindByNamesAsync( - It.Is>(scopes => scopes.Length == 1 && scopes[0] == "scope_registered_in_database"), - It.IsAny())) - .Returns(new[] { scope }.ToAsyncEnumerable()); - - mock.Setup(manager => manager.GetNameAsync(scope, It.IsAny())) - .ReturnsAsync("scope_registered_in_database"); - }); + mock.Setup(manager => manager.GetNameAsync(scope, It.IsAny())) + .ReturnsAsync("scope_registered_in_database"); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.Services.AddSingleton(CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - })); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - options.RegisterScopes("scope_registered_in_options"); - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableAuthorizationStorage(); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + options.RegisterScopes("scope_registered_in_options"); + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableAuthorizationStorage(); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); - options.Services.AddSingleton(manager); + options.Services.AddSingleton(manager); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam", - Scope = "scope_registered_in_database scope_registered_in_options" - }); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam", + Scope = "scope_registered_in_database scope_registered_in_options" + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.DeviceCode); + Assert.NotNull(response.UserCode); + } - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.DeviceCode); - Assert.NotNull(response.UserCode); - } + [Fact] + public async Task ValidateDeviceRequest_RequestIsRejectedWhenClientCannotBeFound() + { + // Arrange + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(value: null); + }); - [Fact] - public async Task ValidateDeviceRequest_RequestIsRejectedWhenClientCannotBeFound() + await using var server = await CreateServerAsync(options => { - // Arrange - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(value: null); - }); + options.Services.AddSingleton(manager); + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - } + [Fact] + public async Task ValidateDeviceRequest_ClientSecretCannotBeUsedByPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateDeviceRequest_ClientSecretCannotBeUsedByPublicClients() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + }); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw" + }); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw" - }); + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateDeviceRequest_ClientSecretIsRequiredForNonPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateDeviceRequest_ClientSecretIsRequiredForNonPublicClients() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(false); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = null + }); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = null - }); + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateDeviceRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateDeviceRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(false); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(false); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2055), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2055), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); + } - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw" - }); + [Fact] + public async Task ValidateDeviceRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2055), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2055), response.ErrorUri); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); + + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Device, It.IsAny())) + .ReturnsAsync(false); + }); - [Fact] - public async Task ValidateDeviceRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); + options.Services.AddSingleton(manager); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + options.Configure(options => options.IgnoreEndpointPermissions = false); + }); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Device, It.IsAny())) - .ReturnsAsync(false); - }); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2056), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2056), response.ErrorUri); - options.Configure(options => options.IgnoreEndpointPermissions = false); - }); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Device, It.IsAny()), Times.Once()); + } - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ValidateDeviceRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2056), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2056), response.ErrorUri); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Device, It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.DeviceCode, It.IsAny())) + .ReturnsAsync(false); + }); - [Fact] - public async Task ValidateDeviceRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted() + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); + options.Services.AddSingleton(manager); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + options.Configure(options => options.IgnoreGrantTypePermissions = false); + }); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.DeviceCode, It.IsAny())) - .ReturnsAsync(false); - }); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2027), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2027), response.ErrorUri); - options.Configure(options => options.IgnoreGrantTypePermissions = false); - }); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.DeviceCode, It.IsAny()), Times.Once()); + } - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ValidateDeviceRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2027), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2027), response.ErrorUri); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.DeviceCode, It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.DeviceCode, It.IsAny())) + .ReturnsAsync(true); - [Fact] - public async Task ValidateDeviceRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenPermissionIsNotGranted() + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(false); + }); + + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); + options.Services.AddSingleton(manager); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + options.Configure(options => options.IgnoreGrantTypePermissions = false); + }); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.DeviceCode, It.IsAny())) - .ReturnsAsync(true); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam", + Scope = Scopes.OfflineAccess + }); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.RefreshToken, It.IsAny())) - .ReturnsAsync(false); - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2065(Scopes.OfflineAccess), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2065), response.ErrorUri); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.RefreshToken, It.IsAny()), Times.Once()); + } - options.Configure(options => options.IgnoreGrantTypePermissions = false); - }); + [Fact] + public async Task ValidateDeviceRequest_RequestIsRejectedWhenScopePermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - await using var client = await server.CreateClientAsync(); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam", - Scope = Scopes.OfflineAccess - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2065(Scopes.OfflineAccess), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2065), response.ErrorUri); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny())) + .ReturnsAsync(true); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.RefreshToken, It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Email, It.IsAny())) + .ReturnsAsync(false); + }); - [Fact] - public async Task ValidateDeviceRequest_RequestIsRejectedWhenScopePermissionIsNotGranted() + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); + options.Services.AddSingleton(manager); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + options.RegisterScopes(Scopes.Email, Scopes.Profile); + options.Configure(options => options.IgnoreScopePermissions = false); + }); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny())) - .ReturnsAsync(true); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam", + Scope = "openid offline_access profile email" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2051), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2051), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.OpenId, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.OfflineAccess, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()), Times.Once()); + } - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Email, It.IsAny())) - .ReturnsAsync(false); - }); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateDeviceRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - options.RegisterScopes(Scopes.Email, Scopes.Profile); - options.Configure(options => options.IgnoreScopePermissions = false); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam", - Scope = "openid offline_access profile email" - }); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2051), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2051), response.ErrorUri); - - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.OpenId, It.IsAny()), Times.Never()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.OfflineAccess, It.IsAny()), Times.Never()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()), Times.Once()); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateDeviceRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + [Fact] + public async Task ValidateDeviceRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new + { + name = "Bob le Bricoleur" + }); - await using var client = await server.CreateClientAsync(); + context.HandleRequest(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + return default; + })); + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateDeviceRequest_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - context.HandleRequest(); + [Fact] + public async Task ValidateDeviceRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); + + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task ValidateDeviceRequest_AllowsSkippingHandler() + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleDeviceRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleDeviceRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } + + [Fact] + public async Task HandleDeviceRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Reject(error, description, uri); + name = "Bob le Bricoleur" + }); - return default; - })); - }); + context.HandleRequest(); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); - [Fact] - public async Task HandleDeviceRequest_AllowsHandlingResponse() + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } + + [Fact] + public async Task HandleDeviceRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - context.HandleRequest(); + return default; + })); + }); - return default; - })); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + [Fact] + public async Task HandleDeviceRequest_ResponseContainsCustomParameters() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task HandleDeviceRequest_AllowsSkippingHandler() + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.EnableDegradedMode(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - return default; - })); - }); + options.EnableDegradedMode(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + context.Parameters["custom_parameter"] = "custom_value"; + context.Parameters["parameter_with_multiple_values"] = new[] + { + "custom_value_1", + "custom_value_2" + }; - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + return default; + })); + }); - [Fact] - public async Task HandleDeviceRequest_ResponseContainsCustomParameters() + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); + ClientId = "Fabrikam" + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.DeviceCode); + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); + } - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(CreateApplicationManager(mock => + [Fact] + public async Task ApplyDeviceResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + return default; })); - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); + name = "Bob le Bricoleur" + }); - context.Parameters["custom_parameter"] = "custom_value"; - context.Parameters["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.DeviceCode); - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyDeviceResponse_AllowsHandlingResponse() + [Fact] + public async Task ApplyDeviceResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; + context.Response["parameter_with_multiple_values"] = new[] { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); - - context.HandleRequest(); + "custom_value_1", + "custom_value_2" + }; - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + return default; + })); + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ApplyDeviceResponse_ResponseContainsCustomParameters() + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); - - return default; - })); + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Response["custom_parameter"] = "custom_value"; - context.Response["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractVerificationRequest_UnexpectedMethodReturnsAnError(string method) + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.SendAsync(method, "/connect/verification", new OpenIddictRequest()); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } - - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractVerificationRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/connect/verification", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractVerificationRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractVerificationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ExtractVerificationRequest_AllowsHandlingResponse() + [Fact] + public async Task ExtractVerificationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/connect/verification"); + // Act + var response = await client.GetAsync("/connect/verification"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractVerificationRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractVerificationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/connect/verification"); + // Act + var response = await client.GetAsync("/connect/verification"); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateVerificationRequest_AllowsRejectingRequest(string error, string description, string uri) + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateVerificationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ValidateVerificationRequest_AllowsHandlingResponse() + [Fact] + public async Task ValidateVerificationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ValidateVerificationRequest_AllowsSkippingHandler() + [Fact] + public async Task ValidateVerificationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleVerificationRequest_AllowsRejectingRequest(string error, string description, string uri) + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleVerificationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task HandleVerificationRequest_AllowsHandlingResponse() + [Fact] + public async Task HandleVerificationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task HandleVerificationRequest_AllowsSkippingHandler() + [Fact] + public async Task HandleVerificationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task HandleVerificationRequest_ResponseContainsCustomParameters() + [Fact] + public async Task HandleVerificationRequest_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("WDJB-MJHT", context.Token); - Assert.Equal(new[] { TokenTypeHints.UserCode }, context.ValidTokenTypes); + Assert.Equal("WDJB-MJHT", context.Token); + Assert.Equal(new[] { TokenTypeHints.UserCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) - .SetTokenType(TokenTypeHints.UserCode); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) + .SetTokenType(TokenTypeHints.UserCode); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - context.Parameters["custom_parameter"] = "custom_value"; - context.Parameters["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - })); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + context.Parameters["custom_parameter"] = "custom_value"; + context.Parameters["parameter_with_multiple_values"] = new[] { - context.Transaction.SetProperty("custom_response", context.Response); - context.HandleRequest(); + "custom_value_1", + "custom_value_2" + }; - return default; - })); - }); + return default; + })); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", context.Response); + context.HandleRequest(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest - { - UserCode = "WDJB-MJHT" - }); + return default; + })); + }); - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ApplyVerificationResponse_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + UserCode = "WDJB-MJHT" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(); + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); + } - return default; - })); + [Fact] + public async Task ApplyVerificationResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + return default; + })); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyVerificationResponse_ResponseContainsCustomParameters() + [Fact] + public async Task ApplyVerificationResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Response["custom_parameter"] = "custom_value"; + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/verification", new OpenIddictRequest()); - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - } + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs index 40c07ac6..85251dd3 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs @@ -18,1484 +18,1483 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Post))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractConfigurationRequest_UnexpectedMethodReturnsAnError(string method) { - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Post))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractConfigurationRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/.well-known/openid-configuration", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractConfigurationRequest_AllowsRejectingRequest(string error, string description, string uri) + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.SendAsync(method, "/.well-known/openid-configuration", new OpenIddictRequest()); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractConfigurationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ExtractConfigurationRequest_AllowsHandlingResponse() + [Fact] + public async Task ExtractConfigurationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractConfigurationRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractConfigurationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateConfigurationRequest_AllowsRejectingRequest(string error, string description, string uri) + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateConfigurationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ValidateConfigurationRequest_AllowsHandlingResponse() + [Fact] + public async Task ValidateConfigurationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ValidateConfigurationRequest_AllowsSkippingHandler() + [Fact] + public async Task ValidateConfigurationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task HandleConfigurationRequest_IssuerIsAutomaticallyInferred() - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task HandleConfigurationRequest_IssuerIsAutomaticallyInferred() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal(client.HttpClient.BaseAddress!.AbsoluteUri, (string?) response[Metadata.Issuer]); - } + // Assert + Assert.Equal(client.HttpClient.BaseAddress!.AbsoluteUri, (string?) response[Metadata.Issuer]); + } - [Fact] - public async Task HandleConfigurationRequest_RegisteredIssuerIsAlwaysPreferred() + [Fact] + public async Task HandleConfigurationRequest_RegisteredIssuerIsAlwaysPreferred() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.SetIssuer(new Uri("https://www.fabrikam.com/")); - }); + options.SetIssuer(new Uri("https://www.fabrikam.com/")); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal("https://www.fabrikam.com/", (string?) response[Metadata.Issuer]); - } + // Assert + Assert.Equal("https://www.fabrikam.com/", (string?) response[Metadata.Issuer]); + } - [Fact] - public async Task HandleConfigurationRequest_AbsoluteEndpointsAreCorrectlyExposed() + [Fact] + public async Task HandleConfigurationRequest_AbsoluteEndpointsAreCorrectlyExposed() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.SetAuthorizationEndpointUris("https://www.fabrikam.com/path/authorization_endpoint") - .SetCryptographyEndpointUris("https://www.fabrikam.com/path/cryptography_endpoint") - .SetDeviceEndpointUris("https://www.fabrikam.com/path/device_endpoint") - .SetIntrospectionEndpointUris("https://www.fabrikam.com/path/introspection_endpoint") - .SetLogoutEndpointUris("https://www.fabrikam.com/path/logout_endpoint") - .SetRevocationEndpointUris("https://www.fabrikam.com/path/revocation_endpoint") - .SetTokenEndpointUris("https://www.fabrikam.com/path/token_endpoint") - .SetUserinfoEndpointUris("https://www.fabrikam.com/path/userinfo_endpoint"); - }); + options.SetAuthorizationEndpointUris("https://www.fabrikam.com/path/authorization_endpoint") + .SetCryptographyEndpointUris("https://www.fabrikam.com/path/cryptography_endpoint") + .SetDeviceEndpointUris("https://www.fabrikam.com/path/device_endpoint") + .SetIntrospectionEndpointUris("https://www.fabrikam.com/path/introspection_endpoint") + .SetLogoutEndpointUris("https://www.fabrikam.com/path/logout_endpoint") + .SetRevocationEndpointUris("https://www.fabrikam.com/path/revocation_endpoint") + .SetTokenEndpointUris("https://www.fabrikam.com/path/token_endpoint") + .SetUserinfoEndpointUris("https://www.fabrikam.com/path/userinfo_endpoint"); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal("https://www.fabrikam.com/path/authorization_endpoint", - (string?) response[Metadata.AuthorizationEndpoint]); + // Assert + Assert.Equal("https://www.fabrikam.com/path/authorization_endpoint", + (string?) response[Metadata.AuthorizationEndpoint]); - Assert.Equal("https://www.fabrikam.com/path/cryptography_endpoint", - (string?) response[Metadata.JwksUri]); + Assert.Equal("https://www.fabrikam.com/path/cryptography_endpoint", + (string?) response[Metadata.JwksUri]); - Assert.Equal("https://www.fabrikam.com/path/authorization_endpoint", - (string?) response[Metadata.AuthorizationEndpoint]); + Assert.Equal("https://www.fabrikam.com/path/authorization_endpoint", + (string?) response[Metadata.AuthorizationEndpoint]); - Assert.Equal("https://www.fabrikam.com/path/device_endpoint", - (string?) response[Metadata.DeviceAuthorizationEndpoint]); + Assert.Equal("https://www.fabrikam.com/path/device_endpoint", + (string?) response[Metadata.DeviceAuthorizationEndpoint]); - Assert.Equal("https://www.fabrikam.com/path/introspection_endpoint", - (string?) response[Metadata.IntrospectionEndpoint]); + Assert.Equal("https://www.fabrikam.com/path/introspection_endpoint", + (string?) response[Metadata.IntrospectionEndpoint]); - Assert.Equal("https://www.fabrikam.com/path/logout_endpoint", - (string?) response[Metadata.EndSessionEndpoint]); + Assert.Equal("https://www.fabrikam.com/path/logout_endpoint", + (string?) response[Metadata.EndSessionEndpoint]); - Assert.Equal("https://www.fabrikam.com/path/revocation_endpoint", - (string?) response[Metadata.RevocationEndpoint]); + Assert.Equal("https://www.fabrikam.com/path/revocation_endpoint", + (string?) response[Metadata.RevocationEndpoint]); - Assert.Equal("https://www.fabrikam.com/path/token_endpoint", - (string?) response[Metadata.TokenEndpoint]); + Assert.Equal("https://www.fabrikam.com/path/token_endpoint", + (string?) response[Metadata.TokenEndpoint]); - Assert.Equal("https://www.fabrikam.com/path/userinfo_endpoint", - (string?) response[Metadata.UserinfoEndpoint]); - } + Assert.Equal("https://www.fabrikam.com/path/userinfo_endpoint", + (string?) response[Metadata.UserinfoEndpoint]); + } - [Theory] - [InlineData("https://www.fabrikam.com/tenant1", new[] - { - "path/authorization_endpoint", - "path/cryptography_endpoint", - "path/device_endpoint", - "path/introspection_endpoint", - "path/logout_endpoint", - "path/revocation_endpoint", - "path/token_endpoint", - "path/userinfo_endpoint" - })] - [InlineData("https://www.fabrikam.com/tenant1/", new[] - { - "path/authorization_endpoint", - "path/cryptography_endpoint", - "path/device_endpoint", - "path/introspection_endpoint", - "path/logout_endpoint", - "path/revocation_endpoint", - "path/token_endpoint", - "path/userinfo_endpoint" - })] - [InlineData("https://www.fabrikam.com/tenant1", new[] - { - "/path/authorization_endpoint", - "/path/cryptography_endpoint", - "/path/device_endpoint", - "/path/introspection_endpoint", - "/path/logout_endpoint", - "/path/revocation_endpoint", - "/path/token_endpoint", - "/path/userinfo_endpoint" - })] - [InlineData("https://www.fabrikam.com/tenant1/", new[] - { - "/path/authorization_endpoint", - "/path/cryptography_endpoint", - "/path/device_endpoint", - "/path/introspection_endpoint", - "/path/logout_endpoint", - "/path/revocation_endpoint", - "/path/token_endpoint", - "/path/userinfo_endpoint" - })] - public async Task HandleConfigurationRequest_RelativeEndpointsAreCorrectlyComputed(string issuer, string[] endpoints) + [Theory] + [InlineData("https://www.fabrikam.com/tenant1", new[] + { + "path/authorization_endpoint", + "path/cryptography_endpoint", + "path/device_endpoint", + "path/introspection_endpoint", + "path/logout_endpoint", + "path/revocation_endpoint", + "path/token_endpoint", + "path/userinfo_endpoint" + })] + [InlineData("https://www.fabrikam.com/tenant1/", new[] + { + "path/authorization_endpoint", + "path/cryptography_endpoint", + "path/device_endpoint", + "path/introspection_endpoint", + "path/logout_endpoint", + "path/revocation_endpoint", + "path/token_endpoint", + "path/userinfo_endpoint" + })] + [InlineData("https://www.fabrikam.com/tenant1", new[] + { + "/path/authorization_endpoint", + "/path/cryptography_endpoint", + "/path/device_endpoint", + "/path/introspection_endpoint", + "/path/logout_endpoint", + "/path/revocation_endpoint", + "/path/token_endpoint", + "/path/userinfo_endpoint" + })] + [InlineData("https://www.fabrikam.com/tenant1/", new[] + { + "/path/authorization_endpoint", + "/path/cryptography_endpoint", + "/path/device_endpoint", + "/path/introspection_endpoint", + "/path/logout_endpoint", + "/path/revocation_endpoint", + "/path/token_endpoint", + "/path/userinfo_endpoint" + })] + public async Task HandleConfigurationRequest_RelativeEndpointsAreCorrectlyComputed(string issuer, string[] endpoints) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.SetIssuer(new Uri(issuer, UriKind.Absolute)); + options.SetIssuer(new Uri(issuer, UriKind.Absolute)); - options.SetAuthorizationEndpointUris(endpoints[0]) - .SetCryptographyEndpointUris(endpoints[1]) - .SetDeviceEndpointUris(endpoints[2]) - .SetIntrospectionEndpointUris(endpoints[3]) - .SetLogoutEndpointUris(endpoints[4]) - .SetRevocationEndpointUris(endpoints[5]) - .SetTokenEndpointUris(endpoints[6]) - .SetUserinfoEndpointUris(endpoints[7]); - }); + options.SetAuthorizationEndpointUris(endpoints[0]) + .SetCryptographyEndpointUris(endpoints[1]) + .SetDeviceEndpointUris(endpoints[2]) + .SetIntrospectionEndpointUris(endpoints[3]) + .SetLogoutEndpointUris(endpoints[4]) + .SetRevocationEndpointUris(endpoints[5]) + .SetTokenEndpointUris(endpoints[6]) + .SetUserinfoEndpointUris(endpoints[7]); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal("https://www.fabrikam.com/tenant1/path/authorization_endpoint", - (string?) response[Metadata.AuthorizationEndpoint]); + // Assert + Assert.Equal("https://www.fabrikam.com/tenant1/path/authorization_endpoint", + (string?) response[Metadata.AuthorizationEndpoint]); - Assert.Equal("https://www.fabrikam.com/tenant1/path/cryptography_endpoint", - (string?) response[Metadata.JwksUri]); + Assert.Equal("https://www.fabrikam.com/tenant1/path/cryptography_endpoint", + (string?) response[Metadata.JwksUri]); - Assert.Equal("https://www.fabrikam.com/tenant1/path/device_endpoint", - (string?) response[Metadata.DeviceAuthorizationEndpoint]); + Assert.Equal("https://www.fabrikam.com/tenant1/path/device_endpoint", + (string?) response[Metadata.DeviceAuthorizationEndpoint]); - Assert.Equal("https://www.fabrikam.com/tenant1/path/introspection_endpoint", - (string?) response[Metadata.IntrospectionEndpoint]); + Assert.Equal("https://www.fabrikam.com/tenant1/path/introspection_endpoint", + (string?) response[Metadata.IntrospectionEndpoint]); - Assert.Equal("https://www.fabrikam.com/tenant1/path/logout_endpoint", - (string?) response[Metadata.EndSessionEndpoint]); + Assert.Equal("https://www.fabrikam.com/tenant1/path/logout_endpoint", + (string?) response[Metadata.EndSessionEndpoint]); - Assert.Equal("https://www.fabrikam.com/tenant1/path/revocation_endpoint", - (string?) response[Metadata.RevocationEndpoint]); + Assert.Equal("https://www.fabrikam.com/tenant1/path/revocation_endpoint", + (string?) response[Metadata.RevocationEndpoint]); - Assert.Equal("https://www.fabrikam.com/tenant1/path/token_endpoint", - (string?) response[Metadata.TokenEndpoint]); + Assert.Equal("https://www.fabrikam.com/tenant1/path/token_endpoint", + (string?) response[Metadata.TokenEndpoint]); - Assert.Equal("https://www.fabrikam.com/tenant1/path/userinfo_endpoint", - (string?) response[Metadata.UserinfoEndpoint]); - } + Assert.Equal("https://www.fabrikam.com/tenant1/path/userinfo_endpoint", + (string?) response[Metadata.UserinfoEndpoint]); + } - [Fact] - public async Task HandleConfigurationRequest_NoClientAuthenticationMethodIsIncludedWhenTokenEndpointIsDisabled() + [Fact] + public async Task HandleConfigurationRequest_NoClientAuthenticationMethodIsIncludedWhenTokenEndpointIsDisabled() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Configure(options => options.GrantTypes.Clear()); - options.Configure(options => options.GrantTypes.Add(GrantTypes.Implicit)); - options.Configure(options => options.ResponseTypes.Clear()); - options.Configure(options => options.DeviceEndpointUris.Clear()); - options.Configure(options => options.VerificationEndpointUris.Clear()); - options.SetTokenEndpointUris(Array.Empty()); - }); + options.Configure(options => options.GrantTypes.Clear()); + options.Configure(options => options.GrantTypes.Add(GrantTypes.Implicit)); + options.Configure(options => options.ResponseTypes.Clear()); + options.Configure(options => options.DeviceEndpointUris.Clear()); + options.Configure(options => options.VerificationEndpointUris.Clear()); + options.SetTokenEndpointUris(Array.Empty()); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.False(response.HasParameter(Metadata.TokenEndpointAuthMethodsSupported)); - } + // Assert + Assert.False(response.HasParameter(Metadata.TokenEndpointAuthMethodsSupported)); + } - [Fact] - public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenTokenEndpointIsEnabled() - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenTokenEndpointIsEnabled() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var methods = (string[]?) response[Metadata.TokenEndpointAuthMethodsSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var methods = (string[]?) response[Metadata.TokenEndpointAuthMethodsSupported]; - // Assert - Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods); - Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods); - } + // Assert + Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods); + Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods); + } - [Fact] - public async Task HandleConfigurationRequest_NoClientAuthenticationMethodIsIncludedWhenIntrospectionEndpointIsDisabled() + [Fact] + public async Task HandleConfigurationRequest_NoClientAuthenticationMethodIsIncludedWhenIntrospectionEndpointIsDisabled() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.SetIntrospectionEndpointUris(Array.Empty()); - }); + options.SetIntrospectionEndpointUris(Array.Empty()); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.False(response.HasParameter(Metadata.IntrospectionEndpointAuthMethodsSupported)); - } + // Assert + Assert.False(response.HasParameter(Metadata.IntrospectionEndpointAuthMethodsSupported)); + } - [Fact] - public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenIntrospectionEndpointIsEnabled() - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenIntrospectionEndpointIsEnabled() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var methods = (string[]?) response[Metadata.IntrospectionEndpointAuthMethodsSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var methods = (string[]?) response[Metadata.IntrospectionEndpointAuthMethodsSupported]; - // Assert - Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods); - Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods); - } + // Assert + Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods); + Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods); + } - [Fact] - public async Task HandleConfigurationRequest_NoClientAuthenticationMethodIsIncludedWhenRevocationEndpointIsDisabled() + [Fact] + public async Task HandleConfigurationRequest_NoClientAuthenticationMethodIsIncludedWhenRevocationEndpointIsDisabled() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.SetRevocationEndpointUris(Array.Empty()); - }); + options.SetRevocationEndpointUris(Array.Empty()); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.False(response.HasParameter(Metadata.RevocationEndpointAuthMethodsSupported)); - } + // Assert + Assert.False(response.HasParameter(Metadata.RevocationEndpointAuthMethodsSupported)); + } - [Fact] - public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenRevocationEndpointIsEnabled() - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenRevocationEndpointIsEnabled() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var methods = (string[]?) response[Metadata.RevocationEndpointAuthMethodsSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var methods = (string[]?) response[Metadata.RevocationEndpointAuthMethodsSupported]; - // Assert - Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods); - Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods); - } + // Assert + Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods); + Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods); + } - [Fact] - public async Task HandleConfigurationRequest_ConfiguredGrantTypesAreReturned() + [Fact] + public async Task HandleConfigurationRequest_ConfiguredGrantTypesAreReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => - { - options.GrantTypes.Clear(); - options.GrantTypes.Add(GrantTypes.AuthorizationCode); - options.GrantTypes.Add(GrantTypes.Password); - })); + options.GrantTypes.Clear(); + options.GrantTypes.Add(GrantTypes.AuthorizationCode); + options.GrantTypes.Add(GrantTypes.Password); + })); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var types = (string[]?) response[Metadata.GrantTypesSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var types = (string[]?) response[Metadata.GrantTypesSupported]; - // Assert - Assert.Equal(2, types?.Length); - Assert.Contains(GrantTypes.AuthorizationCode, types); - Assert.Contains(GrantTypes.Password, types); - } + // Assert + Assert.Equal(2, types?.Length); + Assert.Contains(GrantTypes.AuthorizationCode, types); + Assert.Contains(GrantTypes.Password, types); + } - [Fact] - public async Task HandleConfigurationRequest_NoSupportedCodeChallengeMethodsPropertyIsReturnedWhenNoMethodIsConfigured() + [Fact] + public async Task HandleConfigurationRequest_NoSupportedCodeChallengeMethodsPropertyIsReturnedWhenNoMethodIsConfigured() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.PostConfigure(options => options.CodeChallengeMethods.Clear()); - }); + options.Services.PostConfigure(options => options.CodeChallengeMethods.Clear()); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.False(response.HasParameter(Metadata.CodeChallengeMethodsSupported)); - } + // Assert + Assert.False(response.HasParameter(Metadata.CodeChallengeMethodsSupported)); + } - [Fact] - public async Task HandleConfigurationRequest_ConfiguredCodeChallengeMethodsAreReturned() + [Fact] + public async Task HandleConfigurationRequest_ConfiguredCodeChallengeMethodsAreReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => - { - options.CodeChallengeMethods.Clear(); - options.CodeChallengeMethods.Add(CodeChallengeMethods.Sha256); - options.CodeChallengeMethods.Add(CodeChallengeMethods.Plain); - })); + options.CodeChallengeMethods.Clear(); + options.CodeChallengeMethods.Add(CodeChallengeMethods.Sha256); + options.CodeChallengeMethods.Add(CodeChallengeMethods.Plain); + })); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var methods = (string[]?) response[Metadata.CodeChallengeMethodsSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var methods = (string[]?) response[Metadata.CodeChallengeMethodsSupported]; - // Assert - Assert.Equal(2, methods?.Length); - Assert.Contains(CodeChallengeMethods.Sha256, methods); - Assert.Contains(CodeChallengeMethods.Plain, methods); - } + // Assert + Assert.Equal(2, methods?.Length); + Assert.Contains(CodeChallengeMethods.Sha256, methods); + Assert.Contains(CodeChallengeMethods.Plain, methods); + } - [Fact] - public async Task HandleConfigurationRequest_NoSupportedResponseModesPropertyIsReturnedWhenNoResponseModeIsConfigured() + [Fact] + public async Task HandleConfigurationRequest_NoSupportedResponseModesPropertyIsReturnedWhenNoResponseModeIsConfigured() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.PostConfigure(options => options.ResponseModes.Clear()); - }); + options.Services.PostConfigure(options => options.ResponseModes.Clear()); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.False(response.HasParameter(Metadata.ResponseModesSupported)); - } + // Assert + Assert.False(response.HasParameter(Metadata.ResponseModesSupported)); + } - [Fact] - public async Task HandleConfigurationRequest_ConfiguredResponseModesAreReturned() + [Fact] + public async Task HandleConfigurationRequest_ConfiguredResponseModesAreReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => - { - options.ResponseModes.Clear(); - options.ResponseModes.Add(ResponseModes.FormPost); - options.ResponseModes.Add(ResponseModes.Fragment); - })); + options.ResponseModes.Clear(); + options.ResponseModes.Add(ResponseModes.FormPost); + options.ResponseModes.Add(ResponseModes.Fragment); + })); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var modes = (string[]?) response[Metadata.ResponseModesSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var modes = (string[]?) response[Metadata.ResponseModesSupported]; - // Assert - Assert.Equal(2, modes?.Length); - Assert.Contains(ResponseModes.FormPost, modes); - Assert.Contains(ResponseModes.Fragment, modes); - } + // Assert + Assert.Equal(2, modes?.Length); + Assert.Contains(ResponseModes.FormPost, modes); + Assert.Contains(ResponseModes.Fragment, modes); + } - [Fact] - public async Task HandleConfigurationRequest_NoSupportedResponseTypesPropertyIsReturnedWhenNoResponseTypeIsConfigured() + [Fact] + public async Task HandleConfigurationRequest_NoSupportedResponseTypesPropertyIsReturnedWhenNoResponseTypeIsConfigured() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.PostConfigure(options => options.ResponseTypes.Clear()); - }); + options.Services.PostConfigure(options => options.ResponseTypes.Clear()); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.False(response.HasParameter(Metadata.ResponseTypesSupported)); - } + // Assert + Assert.False(response.HasParameter(Metadata.ResponseTypesSupported)); + } - [Fact] - public async Task HandleConfigurationRequest_ConfiguredResponseTypesAreReturned() + [Fact] + public async Task HandleConfigurationRequest_ConfiguredResponseTypesAreReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => - { - options.ResponseTypes.Clear(); - options.ResponseTypes.Add(ResponseTypes.Code); - options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken); - })); + options.ResponseTypes.Clear(); + options.ResponseTypes.Add(ResponseTypes.Code); + options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken); + })); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var types = (string[]?) response[Metadata.ResponseTypesSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var types = (string[]?) response[Metadata.ResponseTypesSupported]; - // Assert - Assert.Equal(2, types?.Length); - Assert.Contains(ResponseTypes.Code, types); - Assert.Contains(ResponseTypes.Code + ' ' + ResponseTypes.IdToken, types); - } + // Assert + Assert.Equal(2, types?.Length); + Assert.Contains(ResponseTypes.Code, types); + Assert.Contains(ResponseTypes.Code + ' ' + ResponseTypes.IdToken, types); + } - [Fact] - public async Task HandleConfigurationRequest_NoSupportedScopesPropertyIsReturnedWhenNoScopeIsConfigured() + [Fact] + public async Task HandleConfigurationRequest_NoSupportedScopesPropertyIsReturnedWhenNoScopeIsConfigured() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.PostConfigure(options => options.Scopes.Clear()); - }); + options.Services.PostConfigure(options => options.Scopes.Clear()); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.False(response.HasParameter(Metadata.ScopesSupported)); - } + // Assert + Assert.False(response.HasParameter(Metadata.ScopesSupported)); + } - [Fact] - public async Task HandleConfigurationRequest_ConfiguredScopesAreReturned() + [Fact] + public async Task HandleConfigurationRequest_ConfiguredScopesAreReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => - { - options.Scopes.Clear(); - options.Scopes.Add(Scopes.OpenId); - options.Scopes.Add("custom_scope"); - })); + options.Scopes.Clear(); + options.Scopes.Add(Scopes.OpenId); + options.Scopes.Add("custom_scope"); + })); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var scopes = (string[]?) response[Metadata.ScopesSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var scopes = (string[]?) response[Metadata.ScopesSupported]; - // Assert - Assert.Equal(2, scopes?.Length); - Assert.Contains(Scopes.OpenId, scopes); - Assert.Contains("custom_scope", scopes); - } + // Assert + Assert.Equal(2, scopes?.Length); + Assert.Contains(Scopes.OpenId, scopes); + Assert.Contains("custom_scope", scopes); + } - [Fact] - public async Task HandleConfigurationRequest_NoSupportedClaimsPropertyIsReturnedWhenNoClaimIsConfigured() + [Fact] + public async Task HandleConfigurationRequest_NoSupportedClaimsPropertyIsReturnedWhenNoClaimIsConfigured() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.PostConfigure(options => options.Claims.Clear()); - }); + options.Services.PostConfigure(options => options.Claims.Clear()); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.False(response.HasParameter(Metadata.ClaimsSupported)); - } + // Assert + Assert.False(response.HasParameter(Metadata.ClaimsSupported)); + } - [Fact] - public async Task HandleConfigurationRequest_ConfiguredClaimsAreReturned() + [Fact] + public async Task HandleConfigurationRequest_ConfiguredClaimsAreReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.Services.PostConfigure(options => - { - options.Claims.Clear(); - options.Claims.Add(Claims.Profile); - options.Claims.Add("custom_claim"); - })); + options.Claims.Clear(); + options.Claims.Add(Claims.Profile); + options.Claims.Add("custom_claim"); + })); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var claims = (string[]?) response[Metadata.ClaimsSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var claims = (string[]?) response[Metadata.ClaimsSupported]; - // Assert - Assert.Equal(2, claims?.Length); - Assert.Contains(Claims.Profile, claims); - Assert.Contains("custom_claim", claims); - } + // Assert + Assert.Equal(2, claims?.Length); + Assert.Contains(Claims.Profile, claims); + Assert.Contains("custom_claim", claims); + } - [Fact] - public async Task HandleConfigurationRequest_SupportedSubjectTypesAreCorrectlyReturned() - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var types = (string[]?) response[Metadata.SubjectTypesSupported]; - - // Assert - Assert.Contains(SubjectTypes.Public, types); - } - - [Theory] - [InlineData(Algorithms.RsaSha256)] - [InlineData(Algorithms.RsaSha384)] - [InlineData(Algorithms.RsaSha512)] + [Fact] + public async Task HandleConfigurationRequest_SupportedSubjectTypesAreCorrectlyReturned() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var types = (string[]?) response[Metadata.SubjectTypesSupported]; + + // Assert + Assert.Contains(SubjectTypes.Public, types); + } + + [Theory] + [InlineData(Algorithms.RsaSha256)] + [InlineData(Algorithms.RsaSha384)] + [InlineData(Algorithms.RsaSha512)] #if SUPPORTS_ECDSA - [InlineData(Algorithms.EcdsaSha256)] - [InlineData(Algorithms.EcdsaSha384)] - [InlineData(Algorithms.EcdsaSha512)] + [InlineData(Algorithms.EcdsaSha256)] + [InlineData(Algorithms.EcdsaSha384)] + [InlineData(Algorithms.EcdsaSha512)] #endif - public async Task HandleConfigurationRequest_SigningAlgorithmsAreCorrectlyReturned(string algorithm) - { - // Arrange - var credentials = new SigningCredentials(Mock.Of(), algorithm); + public async Task HandleConfigurationRequest_SigningAlgorithmsAreCorrectlyReturned(string algorithm) + { + // Arrange + var credentials = new SigningCredentials(Mock.Of(), algorithm); - await using var server = await CreateServerAsync(options => - { - options.Configure(options => options.SigningCredentials.Clear()); - options.AddSigningCredentials(credentials); - }); + await using var server = await CreateServerAsync(options => + { + options.Configure(options => options.SigningCredentials.Clear()); + options.AddSigningCredentials(credentials); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var algorithms = (string[]?) response[Metadata.IdTokenSigningAlgValuesSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var algorithms = (string[]?) response[Metadata.IdTokenSigningAlgValuesSupported]; - // Assert - Assert.Contains(algorithm, algorithms); - } + // Assert + Assert.Contains(algorithm, algorithms); + } - [Fact] - public async Task HandleConfigurationRequest_SymmetricSigningKeysAreIgnored() + [Fact] + public async Task HandleConfigurationRequest_SymmetricSigningKeysAreIgnored() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Configure(options => options.SigningCredentials.Clear()); - options.AddSigningKey(new SymmetricSecurityKey(new byte[256 / 8])); - options.AddSigningCredentials(new SigningCredentials(Mock.Of(), Algorithms.RsaSha256)); - }); + options.Configure(options => options.SigningCredentials.Clear()); + options.AddSigningKey(new SymmetricSecurityKey(new byte[256 / 8])); + options.AddSigningCredentials(new SigningCredentials(Mock.Of(), Algorithms.RsaSha256)); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var algorithms = (string[]?) response[Metadata.IdTokenSigningAlgValuesSupported]; + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var algorithms = (string[]?) response[Metadata.IdTokenSigningAlgValuesSupported]; - // Assert - Assert.Single(algorithms); - Assert.Contains(Algorithms.RsaSha256, algorithms); - } + // Assert + Assert.Single(algorithms); + Assert.Contains(Algorithms.RsaSha256, algorithms); + } + + [Fact] + public async Task HandleConfigurationRequest_DuplicateSigningAlgorithmsAreIgnored() + { + // Arrange + var credentials = new SigningCredentials(Mock.Of(), SecurityAlgorithms.RsaSha256Signature); - [Fact] - public async Task HandleConfigurationRequest_DuplicateSigningAlgorithmsAreIgnored() + await using var server = await CreateServerAsync(options => { - // Arrange - var credentials = new SigningCredentials(Mock.Of(), SecurityAlgorithms.RsaSha256Signature); + options.Configure(options => options.SigningCredentials.Clear()); + options.AddSigningCredentials(credentials); + options.AddSigningCredentials(credentials); + options.AddSigningCredentials(credentials); + }); - await using var server = await CreateServerAsync(options => - { - options.Configure(options => options.SigningCredentials.Clear()); - options.AddSigningCredentials(credentials); - options.AddSigningCredentials(credentials); - options.AddSigningCredentials(credentials); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + var algorithms = (string[]?) response[Metadata.IdTokenSigningAlgValuesSupported]; - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - var algorithms = (string[]?) response[Metadata.IdTokenSigningAlgValuesSupported]; + // Assert + Assert.Single(algorithms); + } + + [Fact] + public async Task HandleConfigurationRequest_SupportedParametersAreReturned() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Single(algorithms); - } + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - [Fact] - public async Task HandleConfigurationRequest_SupportedParametersAreReturned() - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - - // Assert - Assert.False((bool?) response[Metadata.ClaimsParameterSupported]); - Assert.False((bool?) response[Metadata.RequestParameterSupported]); - Assert.False((bool?) response[Metadata.RequestUriParameterSupported]); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleConfigurationRequest_AllowsRejectingRequest(string error, string description, string uri) + // Assert + Assert.False((bool?) response[Metadata.ClaimsParameterSupported]); + Assert.False((bool?) response[Metadata.RequestParameterSupported]); + Assert.False((bool?) response[Metadata.RequestUriParameterSupported]); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleConfigurationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task HandleConfigurationRequest_AllowsHandlingResponse() + [Fact] + public async Task HandleConfigurationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task HandleConfigurationRequest_AllowsSkippingHandler() + [Fact] + public async Task HandleConfigurationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task ApplyConfigurationResponse_AllowsHandlingResponse() + [Fact] + public async Task ApplyConfigurationResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyConfigurationResponse_ResponseContainsCustomParameters() + [Fact] + public async Task ApplyConfigurationResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; + context.Response["parameter_with_multiple_values"] = new[] { - context.Response["custom_parameter"] = "custom_value"; - context.Response["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/.well-known/openid-configuration"); - - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - } - - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Post))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractCryptographyRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/.well-known/jwks", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractCryptographyRequest_AllowsRejectingRequest(string error, string description, string uri) + "custom_value_1", + "custom_value_2" + }; + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/.well-known/openid-configuration"); + + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + } + + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Post))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractCryptographyRequest_UnexpectedMethodReturnsAnError(string method) + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.SendAsync(method, "/.well-known/jwks", new OpenIddictRequest()); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractCryptographyRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); + // Act + var response = await client.GetAsync("/.well-known/jwks"); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ExtractCryptographyRequest_AllowsHandlingResponse() + [Fact] + public async Task ExtractCryptographyRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); + // Act + var response = await client.GetAsync("/.well-known/jwks"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractCryptographyRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractCryptographyRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/.well-known/jwks"); - - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateCryptographyRequest_AllowsRejectingRequest(string error, string description, string uri) + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/.well-known/jwks"); + + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateCryptographyRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); + // Act + var response = await client.GetAsync("/.well-known/jwks"); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ValidateCryptographyRequest_AllowsHandlingResponse() + [Fact] + public async Task ValidateCryptographyRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); + // Act + var response = await client.GetAsync("/.well-known/jwks"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ValidateCryptographyRequest_AllowsSkippingHandler() + [Fact] + public async Task ValidateCryptographyRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); + // Act + var response = await client.GetAsync("/.well-known/jwks"); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Theory] - [InlineData(SecurityAlgorithms.HmacSha256Signature)] - [InlineData(SecurityAlgorithms.HmacSha384Signature)] - [InlineData(SecurityAlgorithms.HmacSha512Signature)] + [Theory] + [InlineData(SecurityAlgorithms.HmacSha256Signature)] + [InlineData(SecurityAlgorithms.HmacSha384Signature)] + [InlineData(SecurityAlgorithms.HmacSha512Signature)] #if !SUPPORTS_ECDSA - [InlineData(SecurityAlgorithms.EcdsaSha256Signature)] - [InlineData(SecurityAlgorithms.EcdsaSha384Signature)] - [InlineData(SecurityAlgorithms.EcdsaSha512Signature)] + [InlineData(SecurityAlgorithms.EcdsaSha256Signature)] + [InlineData(SecurityAlgorithms.EcdsaSha384Signature)] + [InlineData(SecurityAlgorithms.EcdsaSha512Signature)] #endif - public async Task HandleCryptographyRequest_UnsupportedSecurityKeysAreIgnored(string algorithm) - { - // Arrange - var key = Mock.Of(mock => !mock.IsSupportedAlgorithm(algorithm)); + public async Task HandleCryptographyRequest_UnsupportedSecurityKeysAreIgnored(string algorithm) + { + // Arrange + var key = Mock.Of(mock => !mock.IsSupportedAlgorithm(algorithm)); - await using var server = await CreateServerAsync(options => - { - options.AddSigningCredentials(new SigningCredentials(key, algorithm)); - }); + await using var server = await CreateServerAsync(options => + { + options.AddSigningCredentials(new SigningCredentials(key, algorithm)); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); - var keys = (JsonElement) response[Parameters.Keys]; + // Act + var response = await client.GetAsync("/.well-known/jwks"); + var keys = (JsonElement) response[Parameters.Keys]; - // Assert - Assert.Equal(1, keys.GetArrayLength()); - Assert.Equal(Algorithms.RsaSha256, keys[0].GetProperty(JsonWebKeyParameterNames.Alg).GetString()); - } + // Assert + Assert.Equal(1, keys.GetArrayLength()); + Assert.Equal(Algorithms.RsaSha256, keys[0].GetProperty(JsonWebKeyParameterNames.Alg).GetString()); + } - [Fact] - public async Task HandleCryptographyRequest_RsaSecurityKeysAreCorrectlyExposed() + [Fact] + public async Task HandleCryptographyRequest_RsaSecurityKeysAreCorrectlyExposed() + { + // Arrange + var parameters = new RSAParameters { - // Arrange - var parameters = new RSAParameters - { - D = Convert.FromBase64String("Uj6NrYBnyddhlJefYEP2nleCntAKlWyIttJC4cJnNxNN+OT2fQXhpTXRwW4R5YIS3HDqK/Fg2yoYm+OTVntAAgRFKveRx/WKwFo6UpnJc5u3lElhFa7IfosO9qXjErpX9ruAVqipekDLwQ++KmVVdgH4PK/o//nEx5zklGCdlEJURZYJPs9/7g1cx3UwvPp8jM7LgZL5OZRNyI3Jz4efrwiI2/vd8P28lAbpv/Ao4NwUDq/WKEnZ8JYSjLEKnZCfbX1ZEwf0Ic48jEKHmi1WEwpru1fMPoYfakrsY/VEfatPiDs8a5HABP/KaXcM4AZsr7HbzqAaNycV2xgdZimGcQ=="), - DP = Convert.FromBase64String("hi1e+0eQ/iYrfT4zpZVbx3dyfA7Ch/aujMt6nGMF+1LGaut86vDHM2JI0Gc2BKc+uPEu2bNAorhSmuSyGpfGYl0MYFQoVF/jyiGpzYPmhYpL5yLuN9jWAqNwjfstuRDLU9zTEfZnr3OSN85rZcgT7NUxlY8im1Y2TWYxGiEXw9E="), - DQ = Convert.FromBase64String("laVNkWIbnSuGo7nAxyUSdL2sXU3GZWwItwzTG0IK/0woFjArtCxGgNXW+V+GhxT7iHGAVJJSBvJ65TXrUYuBmoWj2CsoUs2mzK8ax4zg3CXrU61esCsGUoS2owR4FXlhYPmoVnglGu89bH72eXKixZsuF7vKW19nG703BXYEaEU="), - Exponent = Convert.FromBase64String("AQAB"), - InverseQ = Convert.FromBase64String("dhzLDS4F5WYHX+vH4+uL3Ei/K5lxw2A/dBHGtbS2X54gm7vARl+FrptOFFwIjjmsLuTjttAq9K1EP/XZIq8bjW6dXJ/IytnobIPSFkclEeQlMi4/2VDMG5915J0DwnKO9M+B8F3JViUyMv0pvb+ub+HHDVFkIr7zooCmY25i77Q="), - Modulus = Convert.FromBase64String("kXv7Pxf6mSf7mu6mPAOAoKAXl5kU7Q3h9zevC5i4Mm5bMk17XCh7ZvVxDzGA+1JmyxOX6sw3gMUl31FtIFlDhis8VnXKAPn8i1zrmebq+7QKzpE2GpoIpXjXbkPaHG/DbC67M1bux7/dE7lSUSifHRRLsbMUC2D4UahJ6miH2iPFNFyoa6CLtwosD8tIJKwmZ9r9zfqc9BrVGu24lZySjTSRttpLaTkgkBjxHmYhinKNEtj9wUfi1S1wPJUvf+roc6o+7jeBBV3EXJCsb6XCCXI7/e3umWp19odeRShXLQNQbNuuVC7yre4iidUDrWJ1jiaB06svUG+fVEi4FCMvEQ=="), - P = Convert.FromBase64String("xQGczmp4qD7Sez/ZqgW+O4cciTHvSqJqJUSdDd2l1Pd/szQ8avvzorrbSWOIULyv6eJb32+HuyLgy6rTSJ6THFobAnUv4ZTR7EGK26AJmP/BhD+3G+n21+4fzfbAxpHihkCYmO8aEl8fm/r4qPVXmCzFoXDZLMNIxFsdEXiFRS0="), - Q = Convert.FromBase64String("vQy5C++AzF+TRh6qwbKzOqt87ZHEHidIAh6ivRNewjzIgCWXpseVl7DimY1YdViOnw1VI7xY+EyiyTanq5caTqqB3KcDm2t40bJfrZuUcn/5puRIh1bKNDwIMLsuNCrjHmDlNbocqpYMOh0Pgw7ARNbqrnPjWsYGJPuMNFpax/U=") - }; - - await using var server = await CreateServerAsync(options => - { - options.Configure(options => options.SigningCredentials.Clear()); - options.AddSigningKey(new RsaSecurityKey(parameters)); - }); + D = Convert.FromBase64String("Uj6NrYBnyddhlJefYEP2nleCntAKlWyIttJC4cJnNxNN+OT2fQXhpTXRwW4R5YIS3HDqK/Fg2yoYm+OTVntAAgRFKveRx/WKwFo6UpnJc5u3lElhFa7IfosO9qXjErpX9ruAVqipekDLwQ++KmVVdgH4PK/o//nEx5zklGCdlEJURZYJPs9/7g1cx3UwvPp8jM7LgZL5OZRNyI3Jz4efrwiI2/vd8P28lAbpv/Ao4NwUDq/WKEnZ8JYSjLEKnZCfbX1ZEwf0Ic48jEKHmi1WEwpru1fMPoYfakrsY/VEfatPiDs8a5HABP/KaXcM4AZsr7HbzqAaNycV2xgdZimGcQ=="), + DP = Convert.FromBase64String("hi1e+0eQ/iYrfT4zpZVbx3dyfA7Ch/aujMt6nGMF+1LGaut86vDHM2JI0Gc2BKc+uPEu2bNAorhSmuSyGpfGYl0MYFQoVF/jyiGpzYPmhYpL5yLuN9jWAqNwjfstuRDLU9zTEfZnr3OSN85rZcgT7NUxlY8im1Y2TWYxGiEXw9E="), + DQ = Convert.FromBase64String("laVNkWIbnSuGo7nAxyUSdL2sXU3GZWwItwzTG0IK/0woFjArtCxGgNXW+V+GhxT7iHGAVJJSBvJ65TXrUYuBmoWj2CsoUs2mzK8ax4zg3CXrU61esCsGUoS2owR4FXlhYPmoVnglGu89bH72eXKixZsuF7vKW19nG703BXYEaEU="), + Exponent = Convert.FromBase64String("AQAB"), + InverseQ = Convert.FromBase64String("dhzLDS4F5WYHX+vH4+uL3Ei/K5lxw2A/dBHGtbS2X54gm7vARl+FrptOFFwIjjmsLuTjttAq9K1EP/XZIq8bjW6dXJ/IytnobIPSFkclEeQlMi4/2VDMG5915J0DwnKO9M+B8F3JViUyMv0pvb+ub+HHDVFkIr7zooCmY25i77Q="), + Modulus = Convert.FromBase64String("kXv7Pxf6mSf7mu6mPAOAoKAXl5kU7Q3h9zevC5i4Mm5bMk17XCh7ZvVxDzGA+1JmyxOX6sw3gMUl31FtIFlDhis8VnXKAPn8i1zrmebq+7QKzpE2GpoIpXjXbkPaHG/DbC67M1bux7/dE7lSUSifHRRLsbMUC2D4UahJ6miH2iPFNFyoa6CLtwosD8tIJKwmZ9r9zfqc9BrVGu24lZySjTSRttpLaTkgkBjxHmYhinKNEtj9wUfi1S1wPJUvf+roc6o+7jeBBV3EXJCsb6XCCXI7/e3umWp19odeRShXLQNQbNuuVC7yre4iidUDrWJ1jiaB06svUG+fVEi4FCMvEQ=="), + P = Convert.FromBase64String("xQGczmp4qD7Sez/ZqgW+O4cciTHvSqJqJUSdDd2l1Pd/szQ8avvzorrbSWOIULyv6eJb32+HuyLgy6rTSJ6THFobAnUv4ZTR7EGK26AJmP/BhD+3G+n21+4fzfbAxpHihkCYmO8aEl8fm/r4qPVXmCzFoXDZLMNIxFsdEXiFRS0="), + Q = Convert.FromBase64String("vQy5C++AzF+TRh6qwbKzOqt87ZHEHidIAh6ivRNewjzIgCWXpseVl7DimY1YdViOnw1VI7xY+EyiyTanq5caTqqB3KcDm2t40bJfrZuUcn/5puRIh1bKNDwIMLsuNCrjHmDlNbocqpYMOh0Pgw7ARNbqrnPjWsYGJPuMNFpax/U=") + }; + + await using var server = await CreateServerAsync(options => + { + options.Configure(options => options.SigningCredentials.Clear()); + options.AddSigningKey(new RsaSecurityKey(parameters)); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); - var key = response[Parameters.Keys]?[0]; + // Act + var response = await client.GetAsync("/.well-known/jwks"); + var key = response[Parameters.Keys]?[0]; - // Assert - Assert.Null(key?[JsonWebKeyParameterNames.D]); - Assert.Null(key?[JsonWebKeyParameterNames.DP]); - Assert.Null(key?[JsonWebKeyParameterNames.DQ]); - Assert.Null(key?[JsonWebKeyParameterNames.P]); - Assert.Null(key?[JsonWebKeyParameterNames.Q]); + // Assert + Assert.Null(key?[JsonWebKeyParameterNames.D]); + Assert.Null(key?[JsonWebKeyParameterNames.DP]); + Assert.Null(key?[JsonWebKeyParameterNames.DQ]); + Assert.Null(key?[JsonWebKeyParameterNames.P]); + Assert.Null(key?[JsonWebKeyParameterNames.Q]); - Assert.Equal(parameters.Exponent, Base64UrlEncoder.DecodeBytes((string?) key?[JsonWebKeyParameterNames.E])); - Assert.Equal(parameters.Modulus, Base64UrlEncoder.DecodeBytes((string?) key?[JsonWebKeyParameterNames.N])); - } + Assert.Equal(parameters.Exponent, Base64UrlEncoder.DecodeBytes((string?) key?[JsonWebKeyParameterNames.E])); + Assert.Equal(parameters.Modulus, Base64UrlEncoder.DecodeBytes((string?) key?[JsonWebKeyParameterNames.N])); + } #if SUPPORTS_ECDSA - [Theory] - [InlineData( - /* oid: */ "1.2.840.10045.3.1.7", // P-256 - /* d: */ "C0vacBwq1FnQ1N0FHXuuwTlw7Or0neOm2r3AdIKLDKI=", - /* x: */ "7eu+fVtuma+LVD4eH6CxrBX8366cnhPpvgeoeYL7oqw=", - /* y: */ "4qRkITJZ4p5alm0VpLPd+I11wq8vMUHUhbJm1Crx+Zs=")] - [InlineData( - /* oid: */ "1.3.132.0.34", // P-384 - /* d: */ "B2JSdvTbRD/T5Sv7QsGBHPX9yGo2zn3Et5OWrjNauQ2kl+jFkXg5Iy2Vfak7W0ZQ", - /* x: */ "qqsUwddWjXhCWiaUCOUORJIzvp6QDXv1vroHPR4N0C3UqSKkJ5hNiBHaYdRYCnvC", - /* y: */ "QpbQFKBOXgeAKQQub/9QWZPvzNEjXq7aJjHlw4hiY+9QhGPn4qHUaeeI0qlaJ/t2")] - [InlineData( - /* oid: */ "1.3.132.0.35", // P-521 - /* d: */ "ALong1stsWvTLufObn3SPfM8s9VsTG73nXv4mkzGFUmB1r7rda+cpYXU99rFV/kX6zBkFl7Y9TZ2ZyZLFnyUpE4j", - /* x: */ "AS+aCMpMbSO4ga/hUsVIIidqmcQiiT+N9o/5hJ9UVA/vHAKDvWTjuKz+JZfOiR9J+GDUcDZS56UbGG83IosMJMM6", - /* y: */ "AcYkfsb/kTKpcPhYsRPAYV7ibwTN/CdiAM8QuCElAV6wBGfuX1LUmK6ldDVJjytpSz1EmGvzR0T7UCcZcgITqWc2")] - public async Task HandleCryptographyRequest_EcdsaSecurityKeysAreCorrectlyExposed(string oid, string d, string x, string y) + [Theory] + [InlineData( + /* oid: */ "1.2.840.10045.3.1.7", // P-256 + /* d: */ "C0vacBwq1FnQ1N0FHXuuwTlw7Or0neOm2r3AdIKLDKI=", + /* x: */ "7eu+fVtuma+LVD4eH6CxrBX8366cnhPpvgeoeYL7oqw=", + /* y: */ "4qRkITJZ4p5alm0VpLPd+I11wq8vMUHUhbJm1Crx+Zs=")] + [InlineData( + /* oid: */ "1.3.132.0.34", // P-384 + /* d: */ "B2JSdvTbRD/T5Sv7QsGBHPX9yGo2zn3Et5OWrjNauQ2kl+jFkXg5Iy2Vfak7W0ZQ", + /* x: */ "qqsUwddWjXhCWiaUCOUORJIzvp6QDXv1vroHPR4N0C3UqSKkJ5hNiBHaYdRYCnvC", + /* y: */ "QpbQFKBOXgeAKQQub/9QWZPvzNEjXq7aJjHlw4hiY+9QhGPn4qHUaeeI0qlaJ/t2")] + [InlineData( + /* oid: */ "1.3.132.0.35", // P-521 + /* d: */ "ALong1stsWvTLufObn3SPfM8s9VsTG73nXv4mkzGFUmB1r7rda+cpYXU99rFV/kX6zBkFl7Y9TZ2ZyZLFnyUpE4j", + /* x: */ "AS+aCMpMbSO4ga/hUsVIIidqmcQiiT+N9o/5hJ9UVA/vHAKDvWTjuKz+JZfOiR9J+GDUcDZS56UbGG83IosMJMM6", + /* y: */ "AcYkfsb/kTKpcPhYsRPAYV7ibwTN/CdiAM8QuCElAV6wBGfuX1LUmK6ldDVJjytpSz1EmGvzR0T7UCcZcgITqWc2")] + public async Task HandleCryptographyRequest_EcdsaSecurityKeysAreCorrectlyExposed(string oid, string d, string x, string y) + { + // Arrange + var parameters = new ECParameters { - // Arrange - var parameters = new ECParameters + Curve = ECCurve.CreateFromValue(oid), + D = Convert.FromBase64String(d), + Q = new ECPoint { - Curve = ECCurve.CreateFromValue(oid), - D = Convert.FromBase64String(d), - Q = new ECPoint - { - X = Convert.FromBase64String(x), - Y = Convert.FromBase64String(y) - } - }; + X = Convert.FromBase64String(x), + Y = Convert.FromBase64String(y) + } + }; - using var algorithm = ECDsa.Create(parameters); + using var algorithm = ECDsa.Create(parameters); - await using var server = await CreateServerAsync(options => - { - options.Configure(options => options.SigningCredentials.Clear()); - options.AddSigningKey(new ECDsaSecurityKey(algorithm)); - }); + await using var server = await CreateServerAsync(options => + { + options.Configure(options => options.SigningCredentials.Clear()); + options.AddSigningKey(new ECDsaSecurityKey(algorithm)); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); - var key = response[Parameters.Keys]?[0]; + // Act + var response = await client.GetAsync("/.well-known/jwks"); + var key = response[Parameters.Keys]?[0]; - // Assert - Assert.Null(key?[JsonWebKeyParameterNames.D]); + // Assert + Assert.Null(key?[JsonWebKeyParameterNames.D]); - Assert.Equal(parameters.Q.X, Base64UrlEncoder.DecodeBytes((string?) key?[JsonWebKeyParameterNames.X])); - Assert.Equal(parameters.Q.Y, Base64UrlEncoder.DecodeBytes((string?) key?[JsonWebKeyParameterNames.Y])); - } + Assert.Equal(parameters.Q.X, Base64UrlEncoder.DecodeBytes((string?) key?[JsonWebKeyParameterNames.X])); + Assert.Equal(parameters.Q.Y, Base64UrlEncoder.DecodeBytes((string?) key?[JsonWebKeyParameterNames.Y])); + } #endif - [Fact] - public async Task HandleCryptographyRequest_X509CertificatesAreCorrectlyExposed() - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/.well-known/jwks"); - var key = response[Parameters.Keys]?[0]; - - // Assert - Assert.Equal("BSxeQhXNDB4VBeCOavOtvvv9eCI", (string?) key?[JsonWebKeyParameterNames.X5t]); - Assert.Equal("MIIDPjCCAiqgAwIBAgIQlLEp+P+WKYtEAemhSKSUTTAJBgUrDgMCHQUAMC0xKzApBgNVBAMTIk93aW4uU2VjdXJpdHkuT3BlbklkQ29ubmVjdC5TZXJ2ZXIwHhcNOTkxMjMxMjIwMDAwWhcNNDkxMjMxMjIwMDAwWjAtMSswKQYDVQQDEyJPd2luLlNlY3VyaXR5Lk9wZW5JZENvbm5lY3QuU2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwD/4uMNSIu+JlPRrtFR8Tm2LAwSOmglvJai6edFrdvDvk6xWzxYkMoIt4v13lFiIAUfI1vyZ1M0hWQfrifyweuzZu06DyWTUZkp9ervhTxK27HFN7XTuaRxHaXLR4KnhA+Nk8bBXN895OZh9g9Hf5+zsHpe17zgikwcyZtF+9OEG16oz7lKRgXGCIeeVZuSZ5Qf4yePwKMZqsx+lTOiZJ3JMs+gytvIpdZ1NWzcMX0XTcVTgvnBeU0O3NR6DQ41+SrGsojk11bd6kP6mVmDkA0K9kc2eh7q1wyJOeTNuCKRqLthwJ5m46/KRsxgY7ND6qHc1L60SqsFlYCJNEy7EdwIDAQABo2IwYDBeBgNVHQEEVzBVgBDQX+HKPiztLNvT3jQeBXqToS8wLTErMCkGA1UEAxMiT3dpbi5TZWN1cml0eS5PcGVuSWRDb25uZWN0LlNlcnZlcoIQlLEp+P+WKYtEAemhSKSUTTAJBgUrDgMCHQUAA4IBAQCxbCF5thB+ypGpudLAjv+l3M2VhNITJeR9j7jMlCSMVHvW7iMOL5W++zKvHMMAWuITLgPXTZ4ktsjeVQxWdnS2IcU7SwB9SeLbOMk4lLizoUevkiNaf6v+Hskm5LiH6+k8Zsl0INHyIjF9XlALTh91EqQ820cotDXaQIhHabQy892+dBmGWhSE1kP56IvOPzlLdSTkrcfcOu9gzwPVfuTDWH8Hrmo3FXz/fADmE7ea+yE1ZBeKhaN8kaFTs5zrprJ1BnmegnrjDY3RFgqcTTetahv0VBS0/jHSTIsAXflEPGW7LbHimzcgMytFU4fFtPVbek5eunakhu/JdENbbVmT", (string?) key?[JsonWebKeyParameterNames.X5c]?[0]); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleCryptographyRequest_AllowsRejectingRequest(string error, string description, string uri) + [Fact] + public async Task HandleCryptographyRequest_X509CertificatesAreCorrectlyExposed() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/.well-known/jwks"); + var key = response[Parameters.Keys]?[0]; + + // Assert + Assert.Equal("BSxeQhXNDB4VBeCOavOtvvv9eCI", (string?) key?[JsonWebKeyParameterNames.X5t]); + Assert.Equal("MIIDPjCCAiqgAwIBAgIQlLEp+P+WKYtEAemhSKSUTTAJBgUrDgMCHQUAMC0xKzApBgNVBAMTIk93aW4uU2VjdXJpdHkuT3BlbklkQ29ubmVjdC5TZXJ2ZXIwHhcNOTkxMjMxMjIwMDAwWhcNNDkxMjMxMjIwMDAwWjAtMSswKQYDVQQDEyJPd2luLlNlY3VyaXR5Lk9wZW5JZENvbm5lY3QuU2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwD/4uMNSIu+JlPRrtFR8Tm2LAwSOmglvJai6edFrdvDvk6xWzxYkMoIt4v13lFiIAUfI1vyZ1M0hWQfrifyweuzZu06DyWTUZkp9ervhTxK27HFN7XTuaRxHaXLR4KnhA+Nk8bBXN895OZh9g9Hf5+zsHpe17zgikwcyZtF+9OEG16oz7lKRgXGCIeeVZuSZ5Qf4yePwKMZqsx+lTOiZJ3JMs+gytvIpdZ1NWzcMX0XTcVTgvnBeU0O3NR6DQ41+SrGsojk11bd6kP6mVmDkA0K9kc2eh7q1wyJOeTNuCKRqLthwJ5m46/KRsxgY7ND6qHc1L60SqsFlYCJNEy7EdwIDAQABo2IwYDBeBgNVHQEEVzBVgBDQX+HKPiztLNvT3jQeBXqToS8wLTErMCkGA1UEAxMiT3dpbi5TZWN1cml0eS5PcGVuSWRDb25uZWN0LlNlcnZlcoIQlLEp+P+WKYtEAemhSKSUTTAJBgUrDgMCHQUAA4IBAQCxbCF5thB+ypGpudLAjv+l3M2VhNITJeR9j7jMlCSMVHvW7iMOL5W++zKvHMMAWuITLgPXTZ4ktsjeVQxWdnS2IcU7SwB9SeLbOMk4lLizoUevkiNaf6v+Hskm5LiH6+k8Zsl0INHyIjF9XlALTh91EqQ820cotDXaQIhHabQy892+dBmGWhSE1kP56IvOPzlLdSTkrcfcOu9gzwPVfuTDWH8Hrmo3FXz/fADmE7ea+yE1ZBeKhaN8kaFTs5zrprJ1BnmegnrjDY3RFgqcTTetahv0VBS0/jHSTIsAXflEPGW7LbHimzcgMytFU4fFtPVbek5eunakhu/JdENbbVmT", (string?) key?[JsonWebKeyParameterNames.X5c]?[0]); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleCryptographyRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); + // Act + var response = await client.GetAsync("/.well-known/jwks"); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task HandleCryptographyRequest_AllowsHandlingResponse() + [Fact] + public async Task HandleCryptographyRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); + // Act + var response = await client.GetAsync("/.well-known/jwks"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task HandleCryptographyRequest_AllowsSkippingHandler() + [Fact] + public async Task HandleCryptographyRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); + // Act + var response = await client.GetAsync("/.well-known/jwks"); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task ApplyCryptographyResponse_AllowsHandlingResponse() + [Fact] + public async Task ApplyCryptographyResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/.well-known/jwks"); + // Act + var response = await client.GetAsync("/.well-known/jwks"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyCryptographyResponse_ResponseContainsCustomParameters() + [Fact] + public async Task ApplyCryptographyResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; + context.Response["parameter_with_multiple_values"] = new[] { - context.Response["custom_parameter"] = "custom_value"; - context.Response["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/.well-known/jwks"); - - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } + "custom_value_1", + "custom_value_2" + }; + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.GetAsync("/.well-known/jwks"); + + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs index 4f2f4612..867d3b11 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs @@ -21,4142 +21,4141 @@ using static OpenIddict.Server.OpenIddictServerHandlers; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Get))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractTokenRequest_UnexpectedMethodReturnsAnError(string method) { - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Get))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractTokenRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/connect/token", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractTokenRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + // Act + var response = await client.SendAsync(method, "/connect/token", new OpenIddictRequest()); - return default; - })); - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractTokenRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest()); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ExtractTokenRequest_AllowsHandlingResponse() + [Fact] + public async Task ExtractTokenRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractTokenRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractTokenRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest()); + + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + [Fact] + public async Task ValidateTokenRequest_MissingGrantTypeCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_MissingGrantTypeCausesAnError() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + GrantType = null + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = null - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.GrantType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.GrantType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + [Fact] + public async Task ValidateTokenRequest_MissingClientIdCausesAnErrorForCodeFlowRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_MissingClientIdCausesAnErrorForCodeFlowRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = null, - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode - }); + ClientId = null, + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + [Fact] + public async Task ValidateTokenRequest_MissingCodeCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_MissingCodeCausesAnError() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = null, - GrantType = GrantTypes.AuthorizationCode - }); + ClientId = "Fabrikam", + Code = null, + GrantType = GrantTypes.AuthorizationCode + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.Code), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.Code), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + [Fact] + public async Task ValidateTokenRequest_MissingRefreshTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_MissingRefreshTokenCausesAnError() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = null - }); + GrantType = GrantTypes.RefreshToken, + RefreshToken = null + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.RefreshToken), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.RefreshToken), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + [Theory] + [InlineData(null, null)] + [InlineData("client_id", null)] + [InlineData(null, "client_secret")] + public async Task ValidateTokenRequest_MissingClientCredentialsCauseAnError(string identifier, string secret) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Theory] - [InlineData(null, null)] - [InlineData("client_id", null)] - [InlineData(null, "client_secret")] - public async Task ValidateTokenRequest_MissingClientCredentialsCauseAnError(string identifier, string secret) + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = identifier, - ClientSecret = secret, - GrantType = GrantTypes.ClientCredentials - }); + ClientId = identifier, + ClientSecret = secret, + GrantType = GrantTypes.ClientCredentials + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2057(Parameters.ClientId, Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2057), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2057(Parameters.ClientId, Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2057), response.ErrorUri); - } + [Theory] + [InlineData(null, null)] + [InlineData("username", null)] + [InlineData(null, "password")] + public async Task ValidateTokenRequest_MissingUserCredentialsCauseAnError(string username, string password) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Theory] - [InlineData(null, null)] - [InlineData("username", null)] - [InlineData(null, "password")] - public async Task ValidateTokenRequest_MissingUserCredentialsCauseAnError(string username, string password) + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + GrantType = GrantTypes.Password, + Username = username, + Password = password + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2059(Parameters.Username, Parameters.Password), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2059), response.ErrorUri); + } - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = username, - Password = password - }); + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeRequestIsRejectedWhenPkceIsRequiredAndCodeVerifierIsMissing() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.RequireProofKeyForCodeExchange(); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2059(Parameters.Username, Parameters.Password), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2059), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeRequestIsRejectedWhenPkceIsRequiredAndCodeVerifierIsMissing() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RequireProofKeyForCodeExchange(); - }); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + CodeVerifier = null, + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.CodeVerifier), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeRequestIsValidatedWhenPkceIsNotRequiredAndCodeVerifierIsMissing() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = null, - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + builder.UseInlineHandler(context => + { + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.CodeVerifier), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeRequestIsValidatedWhenPkceIsNotRequiredAndCodeVerifierIsMissing() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + return default; + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - }); + return default; + })); + }); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + CodeVerifier = null, + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.NotNull(response.AccessToken); + } - return default; - })); - }); + [Fact] + public async Task ValidateTokenRequest_InvalidAuthorizationCodeCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2001), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2001), response.ErrorUri); + } - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = null, - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + [Fact] + public async Task ValidateTokenRequest_InvalidRefreshTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.NotNull(response.AccessToken); - } + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2003), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2003), response.ErrorUri); + } - [Fact] - public async Task ValidateTokenRequest_InvalidAuthorizationCodeCausesAnError() + [Fact] + public async Task ValidateTokenRequest_ExpiredAuthorizationCodeCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + options.EnableDegradedMode(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode - }); + builder.UseInlineHandler(context => + { + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2001), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2001), response.ErrorUri); - } + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - [Fact] - public async Task ValidateTokenRequest_InvalidRefreshTokenCausesAnError() - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2003), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2003), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_ExpiredAuthorizationCodeCausesAnError() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2016), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2016), response.ErrorUri); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + [Fact] + public async Task ValidateTokenRequest_ExpiredRefreshTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2016), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2016), response.ErrorUri); - } + [Fact] + public async Task ValidateTokenRequest_ExpiredDeviceCodeCausesAnError() + { + // Arrange + var token = new OpenIddictToken(); - [Fact] - public async Task ValidateTokenRequest_ExpiredRefreshTokenCausesAnError() + var manager = CreateTokenManager(mock => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + mock.Setup(manager => manager.FindByReferenceIdAsync("g43LaWCUrz2RaLILz2L1bg1bOpMSv1hGrH12IIkB9H4", It.IsAny())) + .ReturnsAsync(token); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + mock.Setup(manager => manager.HasTypeAsync(token, TokenTypeHints.DeviceCode, It.IsAny())) + .ReturnsAsync(true); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - return default; - }); + mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) + .ReturnsAsync("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(token); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny())) + .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.DeviceCode); + }); + + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + builder.UseInlineHandler(context => + { + Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token); + Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); - } + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - [Fact] - public async Task ValidateTokenRequest_ExpiredDeviceCodeCausesAnError() - { - // Arrange - var token = new OpenIddictToken(); + return default; + }); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByReferenceIdAsync("g43LaWCUrz2RaLILz2L1bg1bOpMSv1hGrH12IIkB9H4", It.IsAny())) - .ReturnsAsync(token); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - mock.Setup(manager => manager.HasTypeAsync(token, TokenTypeHints.DeviceCode, It.IsAny())) - .ReturnsAsync(true); + options.Services.AddSingleton(manager); + }); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) - .ReturnsAsync("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS"); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.DeviceCode, + DeviceCode = "g43LaWCUrz2RaLILz2L1bg1bOpMSv1hGrH12IIkB9H4" + }); - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); + // Assert + Assert.Equal(Errors.ExpiredToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2017), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2017), response.ErrorUri); - mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny())) - .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Inactive, It.IsAny()), Times.Never()); + } - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.DeviceCode); - }); + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenPresentersAreMissing() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token); - Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters(Enumerable.Empty()) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/connect/token", new OpenIddictRequest { - GrantType = GrantTypes.DeviceCode, - DeviceCode = "g43LaWCUrz2RaLILz2L1bg1bOpMSv1hGrH12IIkB9H4" + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode }); + }); - // Assert - Assert.Equal(Errors.ExpiredToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2017), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2017), response.ErrorUri); - - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Inactive, It.IsAny()), Times.Never()); - } + Assert.Equal(SR.GetResourceString(SR.ID0043), exception.Message); + } - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenPresentersAreMissing() + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCallerIsNotAPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters(Enumerable.Empty()) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Contoso") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2069), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2069), response.ErrorUri); + } - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ValidateTokenRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate + options.AddEventHandler(builder => { - return client.PostAsync("/connect/token", new OpenIddictRequest + builder.UseInlineHandler(context => { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetPresenters("Contoso") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); + + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - Assert.Equal(SR.GetResourceString(SR.ID0043), exception.Message); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCallerIsNotAPresenter() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2071), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2071), response.ErrorUri); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenRedirectUriIsMissing() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Contoso") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur") + .SetClaim(Claims.Private.RedirectUri, "http://www.fabrikam.com/callback"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2069), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2069), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAPresenter() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = null + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.RedirectUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenRedirectUriIsInvalid() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetPresenters("Contoso") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur") + .SetClaim(Claims.Private.RedirectUri, "http://www.fabrikam.com/callback"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - - await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2071), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2071), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenRedirectUriIsMissing() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.contoso.com/redirect_uri" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.FormatID2072(Parameters.RedirectUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2072), response.ErrorUri); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + [Fact] + public async Task ValidateTokenRequest_RequestCausesErrorWhenSendingCodeVerifier() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Private.RedirectUri, "http://www.fabrikam.com/callback"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = null + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.RedirectUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenRedirectUriIsInvalid() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + CodeVerifier = "AbCd97394879834759873497549237098273498072304987523948673248972349857982345", + GrantType = GrantTypes.AuthorizationCode + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2073(Parameters.CodeVerifier, Parameters.CodeChallenge), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2073), response.ErrorUri); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCodeVerifierIsMissing() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Private.RedirectUri, "http://www.fabrikam.com/callback"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur") + .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") + .SetClaim(Claims.Private.CodeChallengeMethod, CodeChallengeMethods.Sha256); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.contoso.com/redirect_uri" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.FormatID2072(Parameters.RedirectUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2072), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RequestCausesErrorWhenSendingCodeVerifier() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + CodeVerifier = null, + GrantType = GrantTypes.AuthorizationCode + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.CodeVerifier), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCodeChallengeMethodIsMIssing() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur") + .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") + .SetClaim(Claims.Private.CodeChallengeMethod, null); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/connect/token", new OpenIddictRequest { ClientId = "Fabrikam", Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = "AbCd97394879834759873497549237098273498072304987523948673248972349857982345", + CodeVerifier = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", GrantType = GrantTypes.AuthorizationCode }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2073(Parameters.CodeVerifier, Parameters.CodeChallenge), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2073), response.ErrorUri); - } + Assert.Equal(SR.GetResourceString(SR.ID0268), exception.Message); + } - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCodeVerifierIsMissing() + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCodeChallengeMethodIsInvalid() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") - .SetClaim(Claims.Private.CodeChallengeMethod, CodeChallengeMethods.Sha256); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur") + .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") + .SetClaim(Claims.Private.CodeChallengeMethod, "custom_code_challenge_method"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/connect/token", new OpenIddictRequest { ClientId = "Fabrikam", Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = null, + CodeVerifier = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", GrantType = GrantTypes.AuthorizationCode }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.CodeVerifier), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + Assert.Equal(SR.GetResourceString(SR.ID0045), exception.Message); + } - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCodeChallengeMethodIsMIssing() + [Theory] + [InlineData(CodeChallengeMethods.Plain, "challenge", "invalid_verifier")] + [InlineData(CodeChallengeMethods.Sha256, "challenge", "invalid_verifier")] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCodeVerifierIsInvalid(string method, string challenge, string verifier) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") - .SetClaim(Claims.Private.CodeChallengeMethod, null); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur") + .SetClaim(Claims.Private.CodeChallenge, challenge) + .SetClaim(Claims.Private.CodeChallengeMethod, method); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - - await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - GrantType = GrantTypes.AuthorizationCode - }); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - Assert.Equal(SR.GetResourceString(SR.ID0268), exception.Message); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCodeChallengeMethodIsInvalid() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + CodeVerifier = verifier, + GrantType = GrantTypes.AuthorizationCode + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.CodeVerifier), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + [Theory] + [InlineData(CodeChallengeMethods.Plain, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")] + [InlineData(CodeChallengeMethods.Sha256, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")] + public async Task ValidateTokenRequest_TokenRequestSucceedsWhenCodeVerifierIsValid(string method, string challenge, string verifier) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") - .SetClaim(Claims.Private.CodeChallengeMethod, "custom_code_challenge_method"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur") + .SetClaim(Claims.Private.CodeChallenge, challenge) + .SetClaim(Claims.Private.CodeChallengeMethod, method); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - - await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", - GrantType = GrantTypes.AuthorizationCode - }); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - Assert.Equal(SR.GetResourceString(SR.ID0045), exception.Message); - } - - [Theory] - [InlineData(CodeChallengeMethods.Plain, "challenge", "invalid_verifier")] - [InlineData(CodeChallengeMethods.Sha256, "challenge", "invalid_verifier")] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenCodeVerifierIsInvalid(string method, string challenge, string verifier) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Private.CodeChallenge, challenge) - .SetClaim(Claims.Private.CodeChallengeMethod, method); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = verifier, - GrantType = GrantTypes.AuthorizationCode - }); - - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.CodeVerifier), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + CodeVerifier = verifier, + GrantType = GrantTypes.AuthorizationCode + }); + + // Assert + Assert.NotNull(response.AccessToken); + } - [Theory] - [InlineData(CodeChallengeMethods.Plain, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")] - [InlineData(CodeChallengeMethods.Sha256, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")] - public async Task ValidateTokenRequest_TokenRequestSucceedsWhenCodeVerifierIsValid(string method, string challenge, string verifier) + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenScopeIsUnexpected() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); + options.RegisterScopes(Scopes.Phone, Scopes.Profile); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Private.CodeChallenge, challenge) - .SetClaim(Claims.Private.CodeChallengeMethod, method); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetScopes(Enumerable.Empty()) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = verifier, - GrantType = GrantTypes.AuthorizationCode - }); - - // Assert - Assert.NotNull(response.AccessToken); - } + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + Scope = "profile phone" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.FormatID2074(Parameters.Scope), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2074), response.ErrorUri); + } - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenScopeIsUnexpected() + [Fact] + public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenScopeIsInvalid() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RegisterScopes(Scopes.Phone, Scopes.Profile); + options.EnableDegradedMode(); + options.RegisterScopes(Scopes.Phone, Scopes.Profile); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetScopes(Enumerable.Empty()) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetScopes("profile", "email") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - Scope = "profile phone" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.FormatID2074(Parameters.Scope), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2074), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_AuthorizationCodeCausesAnErrorWhenScopeIsInvalid() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RegisterScopes(Scopes.Phone, Scopes.Profile); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetScopes("profile", "email") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - Scope = "profile phone" - }); - - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - } + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + Scope = "profile phone" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + } - [Fact] - public async Task ValidateTokenRequest_RefreshTokenCausesAnErrorWhenScopeIsUnexpected() + [Fact] + public async Task ValidateTokenRequest_RefreshTokenCausesAnErrorWhenScopeIsUnexpected() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RegisterScopes(Scopes.Phone, Scopes.Profile); + options.EnableDegradedMode(); + options.RegisterScopes(Scopes.Phone, Scopes.Profile); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetScopes(Enumerable.Empty()) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetScopes(Enumerable.Empty()) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8", - Scope = "profile phone" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.FormatID2074(Parameters.Scope), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2074), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RefreshTokenCausesAnErrorWhenScopeIsInvalid() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RegisterScopes(Scopes.Phone, Scopes.Profile); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetScopes("profile", "email") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); - - await using var client = await server.CreateClientAsync(); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8", + Scope = "profile phone" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.FormatID2074(Parameters.Scope), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2074), response.ErrorUri); + } - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8", - Scope = "profile phone" - }); + [Fact] + public async Task ValidateTokenRequest_RefreshTokenCausesAnErrorWhenScopeIsInvalid() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.RegisterScopes(Scopes.Phone, Scopes.Profile); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - } - - [Theory] - [InlineData(GrantTypes.AuthorizationCode)] - [InlineData(GrantTypes.ClientCredentials)] - [InlineData(GrantTypes.Password)] - [InlineData(GrantTypes.RefreshToken)] - public async Task ValidateTokenRequest_RequestIsRejectedWhenFlowIsNotEnabled(string flow) - { - // Arrange - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.EnableDegradedMode(); + builder.UseInlineHandler(context => + { + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - options.Configure(options => options.GrantTypes.Remove(flow)); - options.Configure(options => options.ResponseTypes.Clear()); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetScopes("profile", "email") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = flow, - Username = "johndoe", - Password = "A3ddj3w", - RefreshToken = "8xLOxBtZp8" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(Errors.UnsupportedGrantType, response.Error); - Assert.Equal(SR.FormatID2032(Parameters.GrantType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenFlowIsDisabled() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.Configure(options => options.GrantTypes.Remove(GrantTypes.RefreshToken)); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - Scope = Scopes.OfflineAccess - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2035(Scopes.OfflineAccess), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2035), response.ErrorUri); - } + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8", + Scope = "profile phone" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + } - [Fact] - public async Task ValidateTokenRequest_RequestIsRejectedWhenUnregisteredScopeIsSpecified() + [Theory] + [InlineData(GrantTypes.AuthorizationCode)] + [InlineData(GrantTypes.ClientCredentials)] + [InlineData(GrantTypes.Password)] + [InlineData(GrantTypes.RefreshToken)] + public async Task ValidateTokenRequest_RequestIsRejectedWhenFlowIsNotEnabled(string flow) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(CreateScopeManager(mock => - { - mock.Setup(manager => manager.FindByNamesAsync( - It.Is>(scopes => scopes.Length == 1 && scopes[0] == "unregistered_scope"), - It.IsAny())) - .Returns(AsyncEnumerable.Empty()); - })); - }); - - await using var client = await server.CreateClientAsync(); + options.EnableDegradedMode(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - Scope = "unregistered_scope" - }); + options.Configure(options => options.GrantTypes.Remove(flow)); + options.Configure(options => options.ResponseTypes.Clear()); + }); - // Assert - Assert.Equal(Errors.InvalidScope, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RequestIsValidatedWhenScopeRegisteredInOptionsIsSpecified() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RegisterScopes("registered_scope"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); - }); + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = flow, + Username = "johndoe", + Password = "A3ddj3w", + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.Equal(Errors.UnsupportedGrantType, response.Error); + Assert.Equal(SR.FormatID2032(Parameters.GrantType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri); + } - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ValidateTokenRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenFlowIsDisabled() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - Scope = "registered_scope" - }); + options.Configure(options => options.GrantTypes.Remove(GrantTypes.RefreshToken)); + }); - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RequestIsValidatedWhenRegisteredScopeIsSpecified() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var scope = new OpenIddictScope(); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = Scopes.OfflineAccess + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2035(Scopes.OfflineAccess), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2035), response.ErrorUri); + } - var manager = CreateScopeManager(mock => + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenUnregisteredScopeIsSpecified() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(CreateScopeManager(mock => { mock.Setup(manager => manager.FindByNamesAsync( - It.Is>(scopes => scopes.Length == 1 && scopes[0] == "scope_registered_in_database"), + It.Is>(scopes => scopes.Length == 1 && scopes[0] == "unregistered_scope"), It.IsAny())) - .Returns(new[] { scope }.ToAsyncEnumerable()); - - mock.Setup(manager => manager.GetNameAsync(scope, It.IsAny())) - .ReturnsAsync("scope_registered_in_database"); - }); - - await using var server = await CreateServerAsync(options => - { - options.RegisterScopes("scope_registered_in_options"); - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + .Returns(AsyncEnumerable.Empty()); + })); + }); - options.Services.AddSingleton(manager); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = "unregistered_scope" + }); + + // Assert + Assert.Equal(Errors.InvalidScope, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.Scope), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + } - return default; - })); - }); + [Fact] + public async Task ValidateTokenRequest_RequestIsValidatedWhenScopeRegisteredInOptionsIsSpecified() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.RegisterScopes("registered_scope"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - Scope = "scope_registered_in_database scope_registered_in_options" - }); + return default; + })); + }); - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Theory] - [InlineData("client_id", "")] - [InlineData("", "client_secret")] - public async Task ValidateTokenRequest_ClientCredentialsRequestIsRejectedWhenCredentialsAreMissing(string identifier, string secret) + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = identifier, - ClientSecret = secret, - GrantType = GrantTypes.ClientCredentials - }); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = "registered_scope" + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2057(Parameters.ClientId, Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2057), response.ErrorUri); - } + [Fact] + public async Task ValidateTokenRequest_RequestIsValidatedWhenRegisteredScopeIsSpecified() + { + // Arrange + var scope = new OpenIddictScope(); - [Fact] - public async Task ValidateTokenRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() + var manager = CreateScopeManager(mock => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.Configure(options => options.AcceptAnonymousClients = false); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = null, - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + mock.Setup(manager => manager.FindByNamesAsync( + It.Is>(scopes => scopes.Length == 1 && scopes[0] == "scope_registered_in_database"), + It.IsAny())) + .Returns(new[] { scope }.ToAsyncEnumerable()); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + mock.Setup(manager => manager.GetNameAsync(scope, It.IsAny())) + .ReturnsAsync("scope_registered_in_database"); + }); - [Fact] - public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCannotBeFound() + await using var server = await CreateServerAsync(options => { - // Arrange - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(value: null); - }); - - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + options.RegisterScopes("scope_registered_in_options"); + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + return default; + })); + }); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_ClientCredentialsRequestFromPublicClientIsRejected() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = "scope_registered_in_database scope_registered_in_options" + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); + } - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - }); + [Theory] + [InlineData("client_id", "")] + [InlineData("", "client_secret")] + public async Task ValidateTokenRequest_ClientCredentialsRequestIsRejectedWhenCredentialsAreMissing(string identifier, string secret) + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = identifier, + ClientSecret = secret, + GrantType = GrantTypes.ClientCredentials + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2057(Parameters.ClientId, Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2057), response.ErrorUri); + } - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ValidateTokenRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.Configure(options => options.AcceptAnonymousClients = false); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - GrantType = GrantTypes.ClientCredentials - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.FormatID2043(Parameters.GrantType), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2043), response.ErrorUri); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = null, + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCannotBeFound() + { + // Arrange + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(value: null); + }); - [Fact] - public async Task ValidateTokenRequest_ClientSecretCannotBeUsedByPublicClients() + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); + options.Services.AddSingleton(manager); + }); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + } - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + [Fact] + public async Task ValidateTokenRequest_ClientCredentialsRequestFromPublicClientIsRejected() + { + // Arrange + var application = new OpenIddictApplication(); - await using var client = await server.CreateClientAsync(); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + }); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_ClientSecretIsRequiredForNonPublicClients() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + GrantType = GrantTypes.ClientCredentials + }); + + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.FormatID2043(Parameters.GrantType), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2043), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + [Fact] + public async Task ValidateTokenRequest_ClientSecretCannotBeUsedByPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(false); - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + }); - await using var client = await server.CreateClientAsync(); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = null, - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateTokenRequest_ClientSecretIsRequiredForNonPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(false); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(false); - }); + await using var client = await server.CreateClientAsync(); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = null, + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() + { + // Arrange + var application = new OpenIddictApplication(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2055), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2055), response.ErrorUri); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(false); + }); - [Fact] - public async Task ValidateTokenRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); + options.Services.AddSingleton(manager); + }); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2055), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2055), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); + } - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Token, It.IsAny())) - .ReturnsAsync(false); - }); + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - options.Configure(options => options.IgnoreEndpointPermissions = false); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Token, It.IsAny())) + .ReturnsAsync(false); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2063), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2063), response.ErrorUri); + options.Configure(options => options.IgnoreEndpointPermissions = false); + }); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Token, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + ClientId = "Fabrikam", + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2063), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2063), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Token, It.IsAny()), Times.Once()); + } - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.Password, It.IsAny())) - .ReturnsAsync(false); - }); + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - options.Configure(options => options.IgnoreGrantTypePermissions = false); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(false); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2064), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2064), response.ErrorUri); + options.Configure(options => options.IgnoreGrantTypePermissions = false); + }); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.Password, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenPermissionIsNotGranted() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + ClientId = "Fabrikam", + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2064), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2064), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.Password, It.IsAny()), Times.Once()); + } - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.Password, It.IsAny())) - .ReturnsAsync(true); + [Fact] + public async Task ValidateTokenRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.RefreshToken, It.IsAny())) - .ReturnsAsync(false); - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - options.Configure(options => options.IgnoreGrantTypePermissions = false); - }); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.Password, It.IsAny())) + .ReturnsAsync(true); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.RefreshToken, It.IsAny())) + .ReturnsAsync(false); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - Scope = Scopes.OfflineAccess - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2065(Scopes.OfflineAccess), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2065), response.ErrorUri); + options.Configure(options => options.IgnoreGrantTypePermissions = false); + }); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.GrantTypes.RefreshToken, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RequestIsRejectedWhenScopePermissionIsNotGranted() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + ClientId = "Fabrikam", + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = Scopes.OfflineAccess + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2065(Scopes.OfflineAccess), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2065), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.GrantTypes.RefreshToken, It.IsAny()), Times.Once()); + } - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny())) - .ReturnsAsync(true); + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenScopePermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Email, It.IsAny())) - .ReturnsAsync(false); - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - options.RegisterScopes(Scopes.Email, Scopes.Profile); - options.Configure(options => options.IgnoreScopePermissions = false); - }); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny())) + .ReturnsAsync(true); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Email, It.IsAny())) + .ReturnsAsync(false); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - Scope = "openid offline_access profile email" - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2051), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2051), response.ErrorUri); + options.RegisterScopes(Scopes.Email, Scopes.Profile); + options.Configure(options => options.IgnoreScopePermissions = false); + }); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.OpenId, It.IsAny()), Times.Never()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.OfflineAccess, It.IsAny()), Times.Never()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RequestIsRejectedWhenCodeVerifierIsMissingWithPkceFeatureEnforced() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + ClientId = "Fabrikam", + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = "openid offline_access profile email" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2051), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2051), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.OpenId, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.OfflineAccess, It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()), Times.Once()); + } - mock.Setup(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) - .ReturnsAsync(true); - }); + [Fact] + public async Task ValidateTokenRequest_RequestIsRejectedWhenCodeVerifierIsMissingWithPkceFeatureEnforced() + { + // Arrange + var application = new OpenIddictApplication(); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = null, - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + mock.Setup(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) + .ReturnsAsync(true); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2054(Parameters.CodeVerifier), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_RequestIsValidatedWhenCodeVerifierIsMissingWithPkceFeatureNotEnforced() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + CodeVerifier = null, + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2054(Parameters.CodeVerifier), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once()); + } - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + [Fact] + public async Task ValidateTokenRequest_RequestIsValidatedWhenCodeVerifierIsMissingWithPkceFeatureNotEnforced() + { + // Arrange + var application = new OpenIddictApplication(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) - .ReturnsAsync(false); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - await using var server = await CreateServerAsync(options => + mock.Setup(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) + .ReturnsAsync(false); + }); + + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.Services.AddSingleton(manager); - - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = null, - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); + }); - // Assert - Assert.NotNull(response.AccessToken); + await using var client = await server.CreateClientAsync(); - Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once()); - } - - [Fact] - public async Task ValidateTokenRequest_RequestIsValidatedWhenCodeVerifierIsPresentWithPkceFeatureEnforced() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + CodeVerifier = null, + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.NotNull(response.AccessToken); + + Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once()); + } - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + [Fact] + public async Task ValidateTokenRequest_RequestIsValidatedWhenCodeVerifierIsPresentWithPkceFeatureEnforced() + { + // Arrange + var application = new OpenIddictApplication(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) - .ReturnsAsync(false); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + + mock.Setup(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny())) + .ReturnsAsync(false); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") - .SetClaim(Claims.Private.CodeChallengeMethod, CodeChallengeMethods.Sha256); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetClaim(Claims.Subject, "Bob le Bricoleur") + .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") + .SetClaim(Claims.Private.CodeChallengeMethod, CodeChallengeMethods.Sha256); + + return default; }); - options.Services.AddSingleton(manager); - - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - CodeVerifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); + }); - // Assert - Assert.NotNull(response.AccessToken); - - Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, - Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never()); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateTokenRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + CodeVerifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.NotNull(response.AccessToken); + + Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application, + Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never()); + } - return default; - })); - }); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateTokenRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + return default; + })); + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateTokenRequest_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } + + [Fact] + public async Task ValidateTokenRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ValidateTokenRequest_AllowsSkippingHandler() + [Fact] + public async Task ValidateTokenRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task HandleTokenRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenStorageIsDisabled() + [Fact] + public async Task HandleTokenRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenStorageIsDisabled() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateApplicationManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + return default; })); - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); - }); + options.Services.AddSingleton(CreateApplicationManager(mock => + { + var application = new OpenIddictApplication(); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); + + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); + }); - // Assert - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.NotNull(response.AccessToken); + } - [Fact] - public async Task HandleTokenRequest_RefreshTokenRevocationIsIgnoredWhenTokenStorageIsDisabled() + [Fact] + public async Task HandleTokenRequest_RefreshTokenRevocationIsIgnoredWhenTokenStorageIsDisabled() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.SetRevocationEndpointUris(Array.Empty()); - options.DisableTokenStorage(); - options.DisableSlidingRefreshTokenExpiration(); - }); + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableTokenStorage(); + options.DisableSlidingRefreshTokenExpiration(); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - // Assert - Assert.NotNull(response.AccessToken); - } + // Assert + Assert.NotNull(response.AccessToken); + } - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsUnknown() + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsUnknown() + { + // Arrange + var manager = CreateTokenManager(mock => { - // Arrange - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(value: null); - }); + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(value: null); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateApplicationManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + return default; })); - options.Services.AddSingleton(manager); - }); + options.Services.AddSingleton(CreateApplicationManager(mock => + { + var application = new OpenIddictApplication(); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2001), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2001), response.ErrorUri); + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsUnknown() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(value: null); - }); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2001), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2001), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + } + + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsUnknown() + { + // Arrange + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(value: null); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetPresenters("Fabrikam") - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetPresenters("Fabrikam") + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); - - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + return default; + })); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2003), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2003), response.ErrorUri); + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsAlreadyRedeemed() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var token = new OpenIddictToken(); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2003), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2003), response.ErrorUri); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + } - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(true); - }); + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsAlreadyRedeemed() + { + // Arrange + var token = new OpenIddictToken(); + + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - await using var server = await CreateServerAsync(options => + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(true); + }); + + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateApplicationManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); - - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + return default; })); - options.Services.AddSingleton(manager); - }); + options.Services.AddSingleton(CreateApplicationManager(mock => + { + var application = new OpenIddictApplication(); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2010), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2010), response.ErrorUri); + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.GetRedemptionDateAsync(token, It.IsAny()), Times.Never()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsAlreadyRedeemedAndLeewayIsNull() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var token = new OpenIddictToken(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2010), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2010), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.GetRedemptionDateAsync(token, It.IsAny()), Times.Never()); + } - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsAlreadyRedeemedAndLeewayIsNull() + { + // Arrange + var token = new OpenIddictToken(); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - mock.Setup(manager => manager.GetRedemptionDateAsync(token, It.IsAny())) - .ReturnsAsync(DateTimeOffset.UtcNow); - }); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(true); - await using var server = await CreateServerAsync(options => - { - options.SetRefreshTokenReuseLeeway(leeway: null); + mock.Setup(manager => manager.GetRedemptionDateAsync(token, It.IsAny())) + .ReturnsAsync(DateTimeOffset.UtcNow); + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + await using var server = await CreateServerAsync(options => + { + options.SetRefreshTokenReuseLeeway(leeway: null); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - })); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.Services.AddSingleton(manager); - }); + return default; + })); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2012), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2012), response.ErrorUri); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2012), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2012), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.GetRedemptionDateAsync(token, It.IsAny()), Times.Never()); + } - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.GetRedemptionDateAsync(token, It.IsAny()), Times.Never()); - } + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsAlreadyRedeemedAndCannotBeReused() + { + // Arrange + var token = new OpenIddictToken(); - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsAlreadyRedeemedAndCannotBeReused() + var manager = CreateTokenManager(mock => { - // Arrange - var token = new OpenIddictToken(); + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(token); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetRedemptionDateAsync(token, It.IsAny())) + .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1)); + }); - mock.Setup(manager => manager.GetRedemptionDateAsync(token, It.IsAny())) - .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1)); - }); + await using var server = await CreateServerAsync(options => + { + options.SetRefreshTokenReuseLeeway(TimeSpan.FromSeconds(5)); - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.SetRefreshTokenReuseLeeway(TimeSpan.FromSeconds(5)); - - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - })); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.Services.AddSingleton(manager); - }); + return default; + })); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2012), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2012), response.ErrorUri); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2012), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2012), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.GetRedemptionDateAsync(token, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.GetRedemptionDateAsync(token, It.IsAny()), Times.Once()); - } + [Fact] + public async Task HandleTokenRequest_RequestIsValidatedWhenRefreshTokenIsAlreadyRedeemedAndCanBeReused() + { + // Arrange + var token = new OpenIddictToken(); - [Fact] - public async Task HandleTokenRequest_RequestIsValidatedWhenRefreshTokenIsAlreadyRedeemedAndCanBeReused() + var manager = CreateTokenManager(mock => { - // Arrange - var token = new OpenIddictToken(); + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(token); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetRedemptionDateAsync(token, It.IsAny())) + .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1)); - mock.Setup(manager => manager.GetRedemptionDateAsync(token, It.IsAny())) - .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1)); + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); + }); - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OpenIddictToken()); - }); + await using var server = await CreateServerAsync(options => + { + options.SetRefreshTokenReuseLeeway(TimeSpan.FromMinutes(5)); - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.SetRefreshTokenReuseLeeway(TimeSpan.FromMinutes(5)); - - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); - - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + return default; + })); - // Assert - Assert.NotNull(response.AccessToken); + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.GetRedemptionDateAsync(token, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleTokenRequest_RevokesTokensWhenAuthorizationCodeIsAlreadyRedeemed() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var tokens = ImmutableArray.Create( - new OpenIddictToken(), - new OpenIddictToken(), - new OpenIddictToken()); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(tokens[0]); + // Assert + Assert.NotNull(response.AccessToken); - mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.GetRedemptionDateAsync(token, It.IsAny()), Times.Once()); + } - mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny())) - .ReturnsAsync("47468A64-C9A7-49C7-939C-19CC0F5DD166"); + [Fact] + public async Task HandleTokenRequest_RevokesTokensWhenAuthorizationCodeIsAlreadyRedeemed() + { + // Arrange + var tokens = ImmutableArray.Create( + new OpenIddictToken(), + new OpenIddictToken(), + new OpenIddictToken()); - mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny())) - .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(tokens[0]); - mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny())) + .ReturnsAsync("47468A64-C9A7-49C7-939C-19CC0F5DD166"); - mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .Returns(tokens.ToAsyncEnumerable()); - }); + mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny())) + .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); + + mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + + mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(true); + + mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .Returns(tokens.ToAsyncEnumerable()); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateApplicationManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); - - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + return default; })); - options.Services.AddSingleton(manager); - }); + options.Services.AddSingleton(CreateApplicationManager(mock => + { + var application = new OpenIddictApplication(); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2010), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2010), response.ErrorUri); + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleTokenRequest_RevokesTokensWhenRefreshTokenIsAlreadyRedeemedAndLeewayIsNull() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var tokens = ImmutableArray.Create( - new OpenIddictToken(), - new OpenIddictToken(), - new OpenIddictToken()); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2010), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2010), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Once()); + } - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(tokens[0]); + [Fact] + public async Task HandleTokenRequest_RevokesTokensWhenRefreshTokenIsAlreadyRedeemedAndLeewayIsNull() + { + // Arrange + var tokens = ImmutableArray.Create( + new OpenIddictToken(), + new OpenIddictToken(), + new OpenIddictToken()); - mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(tokens[0]); - mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny())) - .ReturnsAsync("47468A64-C9A7-49C7-939C-19CC0F5DD166"); + mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny())) - .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); + mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny())) + .ReturnsAsync("47468A64-C9A7-49C7-939C-19CC0F5DD166"); - mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny())) + .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); - mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - mock.Setup(manager => manager.GetRedemptionDateAsync(tokens[0], It.IsAny())) - .ReturnsAsync(DateTimeOffset.UtcNow); + mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .Returns(tokens.ToAsyncEnumerable()); - }); + mock.Setup(manager => manager.GetRedemptionDateAsync(tokens[0], It.IsAny())) + .ReturnsAsync(DateTimeOffset.UtcNow); - await using var server = await CreateServerAsync(options => - { - options.SetRefreshTokenReuseLeeway(leeway: null); + mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .Returns(tokens.ToAsyncEnumerable()); + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + await using var server = await CreateServerAsync(options => + { + options.SetRefreshTokenReuseLeeway(leeway: null); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetPresenters("Fabrikam") - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetPresenters("Fabrikam") + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - })); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.Services.AddSingleton(manager); - }); + return default; + })); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2012), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2012), response.ErrorUri); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2012), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2012), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Once()); - } + [Fact] + public async Task HandleTokenRequest_RevokesTokensWhenRefreshTokenIsAlreadyRedeemedAndCannotBeReused() + { + // Arrange + var tokens = ImmutableArray.Create( + new OpenIddictToken(), + new OpenIddictToken(), + new OpenIddictToken()); - [Fact] - public async Task HandleTokenRequest_RevokesTokensWhenRefreshTokenIsAlreadyRedeemedAndCannotBeReused() + var manager = CreateTokenManager(mock => { - // Arrange - var tokens = ImmutableArray.Create( - new OpenIddictToken(), - new OpenIddictToken(), - new OpenIddictToken()); + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(tokens[0]); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(tokens[0]); + mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny())) + .ReturnsAsync("47468A64-C9A7-49C7-939C-19CC0F5DD166"); - mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny())) - .ReturnsAsync("47468A64-C9A7-49C7-939C-19CC0F5DD166"); + mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny())) + .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); - mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny())) - .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); + mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetRedemptionDateAsync(tokens[0], It.IsAny())) + .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1)); - mock.Setup(manager => manager.GetRedemptionDateAsync(tokens[0], It.IsAny())) - .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1)); + mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .Returns(tokens.ToAsyncEnumerable()); + }); - mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .Returns(tokens.ToAsyncEnumerable()); - }); + await using var server = await CreateServerAsync(options => + { + options.SetRefreshTokenReuseLeeway(TimeSpan.FromSeconds(5)); - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.SetRefreshTokenReuseLeeway(TimeSpan.FromSeconds(5)); - - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetPresenters("Fabrikam") - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetPresenters("Fabrikam") + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - })); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.Services.AddSingleton(manager); - }); + return default; + })); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2012), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2012), response.ErrorUri); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2012), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2012), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Once()); - } + [Fact] + public async Task HandleTokenRequest_DoesNotRevokeTokensWhenRefreshTokenIsAlreadyRedeemedAndCanBeReused() + { + // Arrange + var tokens = ImmutableArray.Create( + new OpenIddictToken(), + new OpenIddictToken(), + new OpenIddictToken()); - [Fact] - public async Task HandleTokenRequest_DoesNotRevokeTokensWhenRefreshTokenIsAlreadyRedeemedAndCanBeReused() + var manager = CreateTokenManager(mock => { - // Arrange - var tokens = ImmutableArray.Create( - new OpenIddictToken(), - new OpenIddictToken(), - new OpenIddictToken()); + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(tokens[0]); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(tokens[0]); + mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny())) + .ReturnsAsync("47468A64-C9A7-49C7-939C-19CC0F5DD166"); - mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny())) - .ReturnsAsync("47468A64-C9A7-49C7-939C-19CC0F5DD166"); + mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny())) + .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); - mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny())) - .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8"); + mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetRedemptionDateAsync(tokens[0], It.IsAny())) + .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1)); - mock.Setup(manager => manager.GetRedemptionDateAsync(tokens[0], It.IsAny())) - .ReturnsAsync(DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1)); + mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .Returns(tokens.ToAsyncEnumerable()); - mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .Returns(tokens.ToAsyncEnumerable()); + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); + }); - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OpenIddictToken()); - }); + await using var server = await CreateServerAsync(options => + { + options.SetRefreshTokenReuseLeeway(TimeSpan.FromMinutes(5)); - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.SetRefreshTokenReuseLeeway(TimeSpan.FromMinutes(5)); - - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); - - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateAuthorizationManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var authorization = new OpenIddictAuthorization(); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(authorization); - - mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + return default; })); - }); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.Services.AddSingleton(CreateAuthorizationManager(mock => { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + var authorization = new OpenIddictAuthorization(); + + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(authorization); - // Assert - Assert.NotNull(response.AccessToken); + mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); + })); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Never()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Never()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Never()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsInvalid() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var token = new OpenIddictToken(); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.NotNull(response.AccessToken); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Never()); + } - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsInvalid() + { + // Arrange + var token = new OpenIddictToken(); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(false); - }); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(false); - await using var server = await CreateServerAsync(options => + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(false); + }); + + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateApplicationManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + return default; })); - options.Services.AddSingleton(manager); - }); + options.Services.AddSingleton(CreateApplicationManager(mock => + { + var application = new OpenIddictApplication(); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2016), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2016), response.ErrorUri); + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsInvalid() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var token = new OpenIddictToken(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2016), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2016), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()), Times.Once()); + } - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsInvalid() + { + // Arrange + var token = new OpenIddictToken(); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(false); - }); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(false); + + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(false); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetPresenters("Fabrikam") - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetPresenters("Fabrikam") + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - })); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.Services.AddSingleton(manager); - }); + return default; + })); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()), Times.Once()); - } + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()), Times.Once()); + } - [Fact] - public async Task HandleTokenRequest_AuthorizationAssociatedWithCodeIsIgnoredWhenAuthorizationStorageIsDisabled() + [Fact] + public async Task HandleTokenRequest_AuthorizationAssociatedWithCodeIsIgnoredWhenAuthorizationStorageIsDisabled() + { + // Arrange + var manager = CreateAuthorizationManager(mock => { - // Arrange - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(new OpenIddictAuthorization()); - }); + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(new OpenIddictAuthorization()); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateApplicationManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); - - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + return default; })); - options.Services.AddSingleton(CreateTokenManager(mock => - { - var token = new OpenIddictToken(); + options.Services.AddSingleton(CreateApplicationManager(mock => + { + var application = new OpenIddictApplication(); + + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + options.Services.AddSingleton(CreateTokenManager(mock => + { + var token = new OpenIddictToken(); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(false); - mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OpenIddictToken()); - })); + mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - options.Services.AddSingleton(manager); + mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) + .ReturnsAsync(true); - options.DisableAuthorizationStorage(); - }); + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); + })); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + options.DisableAuthorizationStorage(); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); - // Assert - Assert.NotNull(response.AccessToken); + // Assert + Assert.NotNull(response.AccessToken); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Never()); - } + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Never()); + } + + [Fact] + public async Task HandleTokenRequest_AuthorizationAssociatedWithRefreshTokenIsIgnoredWhenAuthorizationStorageIsDisabled() + { + // Arrange + var authorization = new OpenIddictAuthorization(); - [Fact] - public async Task HandleTokenRequest_AuthorizationAssociatedWithRefreshTokenIsIgnoredWhenAuthorizationStorageIsDisabled() + var manager = CreateAuthorizationManager(mock => { - // Arrange - var authorization = new OpenIddictAuthorization(); + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(new OpenIddictAuthorization()); + }); - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(new OpenIddictAuthorization()); - }); + await using var server = await CreateServerAsync(options => + { + options.DisableRollingRefreshTokens(); - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.DisableRollingRefreshTokens(); - - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateTokenManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var token = new OpenIddictToken(); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + })); - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); + options.Services.AddSingleton(CreateTokenManager(mock => + { + var token = new OpenIddictToken(); + + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(false); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OpenIddictToken()); - })); + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); + })); - options.Services.AddSingleton(manager); + options.Services.AddSingleton(manager); - options.DisableAuthorizationStorage(); - }); + options.DisableAuthorizationStorage(); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - // Assert - Assert.NotNull(response.AccessToken); + // Assert + Assert.NotNull(response.AccessToken); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Never()); - } + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Never()); + } - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithAuthorizationCodeCannotBeFound() + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithAuthorizationCodeCannotBeFound() + { + // Arrange + var manager = CreateAuthorizationManager(mock => { - // Arrange - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(value: null); - }); + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(value: null); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateApplicationManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + return default; })); - options.Services.AddSingleton(CreateTokenManager(mock => - { - var token = new OpenIddictToken(); + options.Services.AddSingleton(CreateApplicationManager(mock => + { + var application = new OpenIddictApplication(); - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.AuthorizationCode); + options.Services.AddSingleton(CreateTokenManager(mock => + { + var token = new OpenIddictToken(); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - })); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.AuthorizationCode); - options.Services.AddSingleton(manager); - }); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(false); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + })); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2020), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2020), response.ErrorUri); + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithAuthorizationCodeIsInvalid() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - var authorization = new OpenIddictAuthorization(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2020), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2020), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); + } - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(authorization); + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithAuthorizationCodeIsInvalid() + { + // Arrange + var authorization = new OpenIddictAuthorization(); - mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny())) - .ReturnsAsync(false); - }); + var manager = CreateAuthorizationManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(authorization); + + mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny())) + .ReturnsAsync(false); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateApplicationManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); - - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + return default; })); - options.Services.AddSingleton(CreateTokenManager(mock => - { - var token = new OpenIddictToken(); + options.Services.AddSingleton(CreateApplicationManager(mock => + { + var application = new OpenIddictApplication(); + + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + options.Services.AddSingleton(CreateTokenManager(mock => + { + var token = new OpenIddictToken(); - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.AuthorizationCode); + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.AuthorizationCode); - mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - })); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(false); - options.Services.AddSingleton(manager); - }); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + })); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + options.Services.AddSingleton(manager); + }); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2020), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2020), response.ErrorUri); + await using var client = await server.CreateClientAsync(); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny()), Times.Once()); - } + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2020), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2020), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny()), Times.Once()); + } - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithRefreshTokenCannotBeFound() + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithRefreshTokenCannotBeFound() + { + // Arrange + var manager = CreateAuthorizationManager(mock => { - // Arrange - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(value: null); - }); + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(value: null); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - options.Services.AddSingleton(CreateTokenManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - var token = new OpenIddictToken(); - - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.RefreshToken); - - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); - - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); - - mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + return default; })); - options.Services.AddSingleton(manager); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.Services.AddSingleton(CreateTokenManager(mock => { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); - - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2022), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2022), response.ErrorUri); + var token = new OpenIddictToken(); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(token); - [Fact] - public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithRefreshTokenIsInvalid() - { - // Arrange - var authorization = new OpenIddictAuthorization(); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(authorization); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.RefreshToken); - mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny())) + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) .ReturnsAsync(false); - }); - await using var server = await CreateServerAsync(options => - { - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + })); - return default; - }); + options.Services.AddSingleton(manager); + }); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - return default; - })); + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2022), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2022), response.ErrorUri); - options.Services.AddSingleton(CreateTokenManager(mock => - { - var token = new OpenIddictToken(); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); + } - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); + [Fact] + public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationAssociatedWithRefreshTokenIsInvalid() + { + // Arrange + var authorization = new OpenIddictAuthorization(); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + var manager = CreateAuthorizationManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(authorization); - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.RefreshToken); + mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - })); + return default; + }); - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + return default; + })); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2022), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2022), response.ErrorUri); - - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny()), Times.Once()); - } - - [Theory] - [InlineData(GrantTypes.AuthorizationCode)] - [InlineData(GrantTypes.ClientCredentials)] - [InlineData(GrantTypes.Password)] - [InlineData(GrantTypes.RefreshToken)] - [InlineData("urn:ietf:params:oauth:grant-type:custom_grant")] - public async Task HandleTokenRequest_RequestsAreSuccessfullyHandled(string flow) - { - // Arrange - var manager = CreateTokenManager(mock => + options.Services.AddSingleton(CreateTokenManager(mock => { var token = new OpenIddictToken(); - mock.Setup(manager => manager.FindByIdAsync("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC", It.IsAny())) + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC"); + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.RefreshToken); + + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(false); mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) .ReturnsAsync(true); mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + })); - mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) - .ReturnsAsync(true); - - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OpenIddictToken()); - }); + options.Services.AddSingleton(manager); + }); - await using var server = await CreateServerAsync(options => - { - options.DisableRollingRefreshTokens(); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(context.Request!.IsAuthorizationCodeGrantType() ? - TokenTypeHints.AuthorizationCode : - TokenTypeHints.RefreshToken) - .SetPresenters("Fabrikam") - .SetTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); - - return default; - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2022), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2022), response.ErrorUri); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny()), Times.Once()); + } - return default; - })); + [Theory] + [InlineData(GrantTypes.AuthorizationCode)] + [InlineData(GrantTypes.ClientCredentials)] + [InlineData(GrantTypes.Password)] + [InlineData(GrantTypes.RefreshToken)] + [InlineData("urn:ietf:params:oauth:grant-type:custom_grant")] + public async Task HandleTokenRequest_RequestsAreSuccessfullyHandled(string flow) + { + // Arrange + var manager = CreateTokenManager(mock => + { + var token = new OpenIddictToken(); - options.Services.AddSingleton(CreateApplicationManager(mock => - { - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByIdAsync("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC"); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(true); - })); + mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - options.Services.AddSingleton(manager); + mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) + .ReturnsAsync(true); - options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); - options.DisableAuthorizationStorage(); - }); + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); + }); - await using var client = await server.CreateClientAsync(); + await using var server = await CreateServerAsync(options => + { + options.DisableRollingRefreshTokens(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Code = "8xLOxBtZp8", - GrantType = flow, - RedirectUri = "http://www.fabrikam.com/path", - RefreshToken = "8xLOxBtZp8", - Username = "johndoe", - Password = "A3ddj3w" + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(context.Request!.IsAuthorizationCodeGrantType() ? + TokenTypeHints.AuthorizationCode : + TokenTypeHints.RefreshToken) + .SetPresenters("Fabrikam") + .SetTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - // Assert - Assert.NotNull(response.AccessToken); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleTokenRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + })); + + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.EnableDegradedMode(); + var application = new OpenIddictApplication(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - return default; - })); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); + + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); + options.DisableAuthorizationStorage(); + }); - // Assert - Assert.Equal(error ?? Errors.InvalidGrant, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleTokenRequest_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Code = "8xLOxBtZp8", + GrantType = flow, + RedirectUri = "http://www.fabrikam.com/path", + RefreshToken = "8xLOxBtZp8", + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.NotNull(response.AccessToken); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleTokenRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(error ?? Errors.InvalidGrant, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } + + [Fact] + public async Task HandleTokenRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task HandleTokenRequest_AllowsSkippingHandler() + [Fact] + public async Task HandleTokenRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task HandleTokenRequest_ResponseContainsCustomParameters() + [Fact] + public async Task HandleTokenRequest_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + context.Parameters["custom_parameter"] = "custom_value"; + context.Parameters["parameter_with_multiple_values"] = new[] { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - context.Parameters["custom_parameter"] = "custom_value"; - context.Parameters["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); + "custom_value_1", + "custom_value_2" + }; - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Null(response.Error); - Assert.Null(response.ErrorDescription); - Assert.Null(response.ErrorUri); - Assert.NotNull(response.AccessToken); - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Null(response.Error); + Assert.Null(response.ErrorDescription); + Assert.Null(response.ErrorUri); + Assert.NotNull(response.AccessToken); + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); + } - [Fact] - public async Task ApplyTokenResponse_AllowsHandlingResponse() + [Fact] + public async Task ApplyTokenResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyTokenResponse_ResponseContainsCustomParameters() + [Fact] + public async Task ApplyTokenResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; + context.Response["parameter_with_multiple_values"] = new[] { - context.Response["custom_parameter"] = "custom_value"; - context.Response["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); + "custom_value_1", + "custom_value_2" + }; - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs index 48fd0628..8c2b04eb 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs @@ -22,942 +22,1039 @@ using static OpenIddict.Server.OpenIddictServerHandlers.Introspection; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractIntrospectionRequest_UnexpectedMethodReturnsAnError(string method) { - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractIntrospectionRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/connect/introspect", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractIntrospectionRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + // Act + var response = await client.SendAsync(method, "/connect/introspect", new OpenIddictRequest()); - return default; - })); - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractIntrospectionRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest()); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ExtractIntrospectionRequest_AllowsHandlingResponse() + [Fact] + public async Task ExtractIntrospectionRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/connect/introspect"); + // Act + var response = await client.GetAsync("/connect/introspect"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractIntrospectionRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractIntrospectionRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/connect/introspect"); + // Act + var response = await client.GetAsync("/connect/introspect"); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task ValidateIntrospectionRequest_MissingTokenCausesAnError() - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ValidateIntrospectionRequest_MissingTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = null - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = null + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.Token), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.Token), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - [Fact] - public async Task ValidateIntrospectionRequest_InvalidTokenCausesAnError() + [Fact] + public async Task ValidateIntrospectionRequest_InvalidTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); - }); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "SlAV32hkKG" - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = "SlAV32hkKG" + }); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri); - } + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri); + } - [Fact] - public async Task ValidateIntrospectionRequest_ExpiredTokenCausesAnError() + [Fact] + public async Task ValidateIntrospectionRequest_ExpiredTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + Assert.Equal("SlAV32hkKG", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "SlAV32hkKG", - TokenTypeHint = TokenTypeHints.RefreshToken - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); - } - - [Theory] - [InlineData(TokenTypeHints.AuthorizationCode)] - [InlineData(TokenTypeHints.DeviceCode)] - [InlineData(TokenTypeHints.IdToken)] - [InlineData(TokenTypeHints.UserCode)] - [InlineData("custom_token")] - public async Task ValidateIntrospectionRequest_UnsupportedTokenTypeCausesAnError(string type) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = "SlAV32hkKG", + TokenTypeHint = TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("5HtRgAtc02", context.Token); + [Theory] + [InlineData(TokenTypeHints.AuthorizationCode)] + [InlineData(TokenTypeHints.DeviceCode)] + [InlineData(TokenTypeHints.IdToken)] + [InlineData(TokenTypeHints.UserCode)] + [InlineData("custom_token")] + public async Task ValidateIntrospectionRequest_UnsupportedTokenTypeCausesAnError(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(type); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("5HtRgAtc02", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(type); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - Token = "5HtRgAtc02" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.UnsupportedTokenType, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2076), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2076), response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + Token = "5HtRgAtc02" + }); + + // Assert + Assert.Equal(Errors.UnsupportedTokenType, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2076), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2076), response.ErrorUri); + } - [Fact] - public async Task ValidateIntrospectionRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter() + [Fact] + public async Task ValidateIntrospectionRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetAudiences("AdventureWorks") - .SetPresenters("Contoso"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetAudiences("AdventureWorks") + .SetPresenters("Contoso"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - Token = "2YotnFZFEjr1zCsicMWpAA", - TokenTypeHint = TokenTypeHints.AccessToken - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2077), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2077), response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = TokenTypeHints.AccessToken + }); + + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2077), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2077), response.ErrorUri); + } - [Fact] - public async Task ValidateIntrospectionRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter() + [Fact] + public async Task ValidateIntrospectionRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal("8xLOxBtZp8", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetPresenters("Contoso"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetPresenters("Contoso"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - Token = "8xLOxBtZp8", - TokenTypeHint = TokenTypeHints.RefreshToken - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2077), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2077), response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + Token = "8xLOxBtZp8", + TokenTypeHint = TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2077), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2077), response.ErrorUri); + } - [Fact] - public async Task ValidateIntrospectionRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() + [Fact] + public async Task ValidateIntrospectionRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() + { + // Arrange + await using var server = await CreateServerAsync(builder => { - // Arrange - await using var server = await CreateServerAsync(builder => - { - builder.Configure(options => options.AcceptAnonymousClients = false); - }); + builder.Configure(options => options.AcceptAnonymousClients = false); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - [Fact] - public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCannotBeFound() + [Fact] + public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCannotBeFound() + { + // Arrange + var manager = CreateApplicationManager(mock => { - // Arrange - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(value: null); - }); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(value: null); + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); - } + [Fact] + public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Introspection, It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Introspection, It.IsAny())) - .ReturnsAsync(false); - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + options.Configure(options => options.IgnoreEndpointPermissions = false); + }); - options.Configure(options => options.IgnoreEndpointPermissions = false); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2075), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2075), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Introspection, It.IsAny()), Times.Once()); + } - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + [Fact] + public async Task ValidateIntrospectionRequest_ClientSecretCannotBeUsedByPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2075), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2075), response.ErrorUri); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Introspection, It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + }); - [Fact] - public async Task ValidateIntrospectionRequest_ClientSecretCannotBeUsedByPublicClients() + await using var server = await CreateServerAsync(builder => { - // Arrange - var application = new OpenIddictApplication(); + builder.Services.AddSingleton(manager); + }); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } - await using var server = await CreateServerAsync(builder => - { - builder.Services.AddSingleton(manager); - }); + [Fact] + public async Task ValidateIntrospectionRequest_ClientSecretIsRequiredForNonPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); - await using var client = await server.CreateClientAsync(); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); + }); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); + await using var server = await CreateServerAsync(builder => + { + builder.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateIntrospectionRequest_ClientSecretIsRequiredForNonPublicClients() + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); + ClientId = "Fabrikam", + ClientSecret = null, + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + [Fact] + public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() + { + // Arrange + var application = new OpenIddictApplication(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(false); - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - await using var server = await CreateServerAsync(builder => - { - builder.Services.AddSingleton(manager); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(false); + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = null, - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + // Assert - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); - } + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); + } - [Fact] - public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateIntrospectionRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); + options.EnableDegradedMode(); - var manager = CreateApplicationManager(mock => + options.AddEventHandler(builder => { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(false); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(false); - }); + return default; + }); - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + return default; + })); + }); - // Assert - - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateIntrospectionRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - return default; - }); + [Fact] + public async Task ValidateIntrospectionRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Reject(error, description, uri); + name = "Bob le Bricoleur" + }); - return default; - })); - }); + context.HandleRequest(); + + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ValidateIntrospectionRequest_AllowsHandlingResponse() + [Fact] + public async Task ValidateIntrospectionRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); - - context.HandleRequest(); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + return default; + })); + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateIntrospectionRequest_AllowsSkippingHandler() + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - return default; - }); + [Fact] + public async Task HandleIntrospectionRequest_BasicClaimsAreCorrectlyReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetAudiences("Fabrikam") + .SetPresenters("Contoso", "AdventureWorks Cycles") + .SetCreationDate(new DateTimeOffset(2016, 1, 1, 0, 0, 0, TimeSpan.Zero)) + .SetExpirationDate(new DateTimeOffset(2017, 1, 1, 0, 0, 0, TimeSpan.Zero)) + .SetClaim(Claims.ClientId, "AdventureWorks Cycles") + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaim(Claims.JwtId, "66B65AED-4033-4E9C-B975-A8CA7FB6FA79"); + + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(ValidateExpirationDate.Descriptor); + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = TokenTypeHints.AccessToken + }); + + // Assert + Assert.Equal(11, response.Count); + Assert.True((bool) response[Claims.Active]); + Assert.Equal("66B65AED-4033-4E9C-B975-A8CA7FB6FA79", (string?) response[Claims.JwtId]); + Assert.Equal(TokenTypes.Bearer, (string?) response[Claims.TokenType]); + Assert.Equal(TokenTypeHints.AccessToken, (string?) response[Claims.TokenUsage]); + Assert.Equal("http://localhost/", (string?) response[Claims.Issuer]); + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal(1451606400, (long) response[Claims.IssuedAt]); + Assert.Equal(1451606400, (long) response[Claims.NotBefore]); + Assert.Equal(1483228800, (long) response[Claims.ExpiresAt]); + Assert.Equal("Fabrikam", (string?) response[Claims.Audience]); + Assert.Equal("AdventureWorks Cycles", (string?) response[Claims.ClientId]); + } - [Fact] - public async Task HandleIntrospectionRequest_BasicClaimsAreCorrectlyReturned() + [Fact] + public async Task HandleIntrospectionRequest_NonBasicRefreshTokenClaimsAreNotReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetAudiences("Fabrikam") - .SetPresenters("Contoso", "AdventureWorks Cycles") - .SetCreationDate(new DateTimeOffset(2016, 1, 1, 0, 0, 0, TimeSpan.Zero)) - .SetExpirationDate(new DateTimeOffset(2017, 1, 1, 0, 0, 0, TimeSpan.Zero)) - .SetClaim(Claims.ClientId, "AdventureWorks Cycles") - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaim(Claims.JwtId, "66B65AED-4033-4E9C-B975-A8CA7FB6FA79"); - - return default; - }); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Username, "Bob") + .SetClaim("custom_claim", "secret_value"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(ValidateExpirationDate.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA", - TokenTypeHint = TokenTypeHints.AccessToken - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Null(response["custom_claim"]); + Assert.Null(response[Claims.Username]); + } - // Assert - Assert.Equal(11, response.Count); - Assert.True((bool) response[Claims.Active]); - Assert.Equal("66B65AED-4033-4E9C-B975-A8CA7FB6FA79", (string?) response[Claims.JwtId]); - Assert.Equal(TokenTypes.Bearer, (string?) response[Claims.TokenType]); - Assert.Equal(TokenTypeHints.AccessToken, (string?) response[Claims.TokenUsage]); - Assert.Equal("http://localhost/", (string?) response[Claims.Issuer]); - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal(1451606400, (long) response[Claims.IssuedAt]); - Assert.Equal(1451606400, (long) response[Claims.NotBefore]); - Assert.Equal(1483228800, (long) response[Claims.ExpiresAt]); - Assert.Equal("Fabrikam", (string?) response[Claims.Audience]); - Assert.Equal("AdventureWorks Cycles", (string?) response[Claims.ClientId]); - } - - [Fact] - public async Task HandleIntrospectionRequest_NonBasicRefreshTokenClaimsAreNotReturned() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + [Fact] + public async Task HandleIntrospectionRequest_NonBasicAccessTokenClaimsAreReturnedToTrustedAudiences() + { + // Arrange + var application = new OpenIddictApplication(); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Username, "Bob") - .SetClaim("custom_claim", "secret_value"); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); - return default; - }); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + }); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - await using var client = await server.CreateClientAsync(); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetAudiences("Fabrikam") + .SetPresenters("Contoso", "AdventureWorks Cycles") + .SetScopes(Scopes.OpenId, Scopes.Profile) + .SetClaim(Claims.Username, "Bob") + .SetClaim("custom_claim", "secret_value"); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA", - TokenTypeHint = TokenTypeHints.RefreshToken + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - // Assert - Assert.Null(response["custom_claim"]); - Assert.Null(response[Claims.Username]); - } + options.Services.AddSingleton(manager); + }); - [Fact] - public async Task HandleIntrospectionRequest_NonBasicAccessTokenClaimsAreReturnedToTrustedAudiences() + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = TokenTypeHints.AccessToken + }); + + // Assert + Assert.Equal("secret_value", (string?) response["custom_claim"]); + Assert.Equal("Bob", (string?) response[Claims.Username]); + Assert.Equal("openid profile", (string?) response[Claims.Scope]); + } - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + [Fact] + public async Task HandleIntrospectionRequest_ClaimValueTypesAreHonored() + { + // Arrange + var application = new OpenIddictApplication(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) - .ReturnsAsync(true); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(true); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); - await using var server = await CreateServerAsync(options => + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + }); + + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetAudiences("Fabrikam") - .SetPresenters("Contoso", "AdventureWorks Cycles") - .SetScopes(Scopes.OpenId, Scopes.Profile) - .SetClaim(Claims.Username, "Bob") - .SetClaim("custom_claim", "secret_value"); + var identity = new ClaimsIdentity("Bearer"); + identity.AddClaim(new Claim("boolean_claim", "true", ClaimValueTypes.Boolean)); + identity.AddClaim(new Claim("integer_claim", "42", ClaimValueTypes.Integer)); + identity.AddClaim(new Claim("array_claim", @"[""Contoso"",""Fabrikam""]", JsonClaimValueTypes.JsonArray)); + identity.AddClaim(new Claim("object_claim", @"{""parameter"":""value""}", JsonClaimValueTypes.Json)); - return default; - }); + context.Principal = new ClaimsPrincipal(identity) + .SetTokenType(TokenTypeHints.AccessToken) + .SetAudiences("Fabrikam"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA", - TokenTypeHint = TokenTypeHints.AccessToken - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal("secret_value", (string?) response["custom_claim"]); - Assert.Equal("Bob", (string?) response[Claims.Username]); - Assert.Equal("openid profile", (string?) response[Claims.Scope]); - } + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = TokenTypeHints.AccessToken + }); + + // Assert + Assert.True((bool) response["boolean_claim"]); + Assert.Equal(JsonValueKind.True, ((JsonElement) response["boolean_claim"]).ValueKind); + Assert.Equal(42, (long) response["integer_claim"]); + Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_claim"]).ValueKind); + Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) response["array_claim"]); + Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_claim"]).ValueKind); + Assert.Equal("value", (string?) response["object_claim"]?["parameter"]); + Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_claim"]).ValueKind); + } + + [Fact] + public async Task HandleIntrospectionRequest_MultipleClaimsAreReturnedAsArrays() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task HandleIntrospectionRequest_ClaimValueTypesAreHonored() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) - .ReturnsAsync(true); - - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(true); - }); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - var identity = new ClaimsIdentity("Bearer"); - identity.AddClaim(new Claim("boolean_claim", "true", ClaimValueTypes.Boolean)); - identity.AddClaim(new Claim("integer_claim", "42", ClaimValueTypes.Integer)); - identity.AddClaim(new Claim("array_claim", @"[""Contoso"",""Fabrikam""]", JsonClaimValueTypes.JsonArray)); - identity.AddClaim(new Claim("object_claim", @"{""parameter"":""value""}", JsonClaimValueTypes.Json)); + var identity = new ClaimsIdentity("Bearer"); + identity.AddClaim(new Claim("boolean_claim", "true", ClaimValueTypes.Boolean)); + identity.AddClaim(new Claim("boolean_claim", "false", ClaimValueTypes.Boolean)); - context.Principal = new ClaimsPrincipal(identity) - .SetTokenType(TokenTypeHints.AccessToken) - .SetAudiences("Fabrikam"); + identity.AddClaim(new Claim("integer_claim", "42", ClaimValueTypes.Integer)); + identity.AddClaim(new Claim("integer_claim", "43", ClaimValueTypes.Integer)); - return default; - }); + identity.AddClaim(new Claim("array_claim", @"[""Contoso"",""Fabrikam""]", JsonClaimValueTypes.JsonArray)); + identity.AddClaim(new Claim("array_claim", @"[""Microsoft"",""Google""]", JsonClaimValueTypes.JsonArray)); + + identity.AddClaim(new Claim("object_claim", @"{""parameter_1"":""value-1""}", JsonClaimValueTypes.Json)); + identity.AddClaim(new Claim("object_claim", @"{""parameter_2"":""value-2""}", JsonClaimValueTypes.Json)); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + context.Principal = new ClaimsPrincipal(identity) + .SetTokenType(TokenTypeHints.AccessToken) + .SetAudiences("Fabrikam"); + + return default; }); - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA", - TokenTypeHint = TokenTypeHints.AccessToken - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.True((bool) response["boolean_claim"]); - Assert.Equal(JsonValueKind.True, ((JsonElement) response["boolean_claim"]).ValueKind); - Assert.Equal(42, (long) response["integer_claim"]); - Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_claim"]).ValueKind); - Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]?) response["array_claim"]); - Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_claim"]).ValueKind); - Assert.Equal("value", (string?) response["object_claim"]?["parameter"]); - Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_claim"]).ValueKind); - } + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = TokenTypeHints.AccessToken + }); + + // Assert + Assert.Equal(JsonValueKind.Array, ((JsonElement) response["boolean_claim"]).ValueKind); + Assert.Equal(2, ((JsonElement) response["boolean_claim"]).GetArrayLength()); + Assert.True(((JsonElement) response["boolean_claim"])[0].GetBoolean()); + Assert.False(((JsonElement) response["boolean_claim"])[1].GetBoolean()); + + Assert.Equal(JsonValueKind.Array, ((JsonElement) response["integer_claim"]).ValueKind); + Assert.Equal(2, ((JsonElement) response["boolean_claim"]).GetArrayLength()); + Assert.Equal(42, ((JsonElement) response["integer_claim"])[0].GetInt64()); + Assert.Equal(43, ((JsonElement) response["integer_claim"])[1].GetInt64()); + + Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_claim"]).ValueKind); + Assert.Equal(2, ((JsonElement) response["array_claim"]).GetArrayLength()); + Assert.Equal(2, ((JsonElement) response["array_claim"])[0].GetArrayLength()); + Assert.Equal("Contoso", ((JsonElement) response["array_claim"])[0][0].GetString()); + Assert.Equal("Fabrikam", ((JsonElement) response["array_claim"])[0][1].GetString()); + Assert.Equal(2, ((JsonElement) response["array_claim"])[1].GetArrayLength()); + Assert.Equal("Microsoft", ((JsonElement) response["array_claim"])[1][0].GetString()); + Assert.Equal("Google", ((JsonElement) response["array_claim"])[1][1].GetString()); + + Assert.Equal(JsonValueKind.Array, ((JsonElement) response["object_claim"]).ValueKind); + Assert.Equal(2, ((JsonElement) response["object_claim"]).GetArrayLength()); + Assert.Equal("value-1", ((JsonElement) response["object_claim"])[0].GetProperty("parameter_1").GetString()); + Assert.Equal("value-2", ((JsonElement) response["object_claim"])[1].GetProperty("parameter_2").GetString()); + } - [Fact] - public async Task HandleIntrospectionRequest_MultipleClaimsAreReturnedAsArrays() + [Fact] + public async Task HandleIntrospectionRequest_RequestIsRejectedWhenReferenceTokenIsUnknown() + { + // Arrange + var manager = CreateTokenManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) + .ReturnsAsync(value: null); + }); - var manager = CreateApplicationManager(mock => + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(CreateApplicationManager(mock => { + var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); @@ -966,422 +1063,271 @@ namespace OpenIddict.Server.IntegrationTests mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); - }); - - await using var server = await CreateServerAsync(options => - { - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + })); - var identity = new ClaimsIdentity("Bearer"); - identity.AddClaim(new Claim("boolean_claim", "true", ClaimValueTypes.Boolean)); - identity.AddClaim(new Claim("boolean_claim", "false", ClaimValueTypes.Boolean)); + options.Services.AddSingleton(manager); - identity.AddClaim(new Claim("integer_claim", "42", ClaimValueTypes.Integer)); - identity.AddClaim(new Claim("integer_claim", "43", ClaimValueTypes.Integer)); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - identity.AddClaim(new Claim("array_claim", @"[""Contoso"",""Fabrikam""]", JsonClaimValueTypes.JsonArray)); - identity.AddClaim(new Claim("array_claim", @"[""Microsoft"",""Google""]", JsonClaimValueTypes.JsonArray)); + await using var client = await server.CreateClientAsync(); - identity.AddClaim(new Claim("object_claim", @"{""parameter_1"":""value-1""}", JsonClaimValueTypes.Json)); - identity.AddClaim(new Claim("object_claim", @"{""parameter_2"":""value-2""}", JsonClaimValueTypes.Json)); - - context.Principal = new ClaimsPrincipal(identity) - .SetTokenType(TokenTypeHints.AccessToken) - .SetAudiences("Fabrikam"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - - options.Services.AddSingleton(manager); - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA", - TokenTypeHint = TokenTypeHints.AccessToken - }); + Mock.Get(manager).Verify(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny()), Times.AtLeastOnce()); + } - // Assert - Assert.Equal(JsonValueKind.Array, ((JsonElement) response["boolean_claim"]).ValueKind); - Assert.Equal(2, ((JsonElement) response["boolean_claim"]).GetArrayLength()); - Assert.True(((JsonElement) response["boolean_claim"])[0].GetBoolean()); - Assert.False(((JsonElement) response["boolean_claim"])[1].GetBoolean()); - - Assert.Equal(JsonValueKind.Array, ((JsonElement) response["integer_claim"]).ValueKind); - Assert.Equal(2, ((JsonElement) response["boolean_claim"]).GetArrayLength()); - Assert.Equal(42, ((JsonElement) response["integer_claim"])[0].GetInt64()); - Assert.Equal(43, ((JsonElement) response["integer_claim"])[1].GetInt64()); - - Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_claim"]).ValueKind); - Assert.Equal(2, ((JsonElement) response["array_claim"]).GetArrayLength()); - Assert.Equal(2, ((JsonElement) response["array_claim"])[0].GetArrayLength()); - Assert.Equal("Contoso", ((JsonElement) response["array_claim"])[0][0].GetString()); - Assert.Equal("Fabrikam", ((JsonElement) response["array_claim"])[0][1].GetString()); - Assert.Equal(2, ((JsonElement) response["array_claim"])[1].GetArrayLength()); - Assert.Equal("Microsoft", ((JsonElement) response["array_claim"])[1][0].GetString()); - Assert.Equal("Google", ((JsonElement) response["array_claim"])[1][1].GetString()); - - Assert.Equal(JsonValueKind.Array, ((JsonElement) response["object_claim"]).ValueKind); - Assert.Equal(2, ((JsonElement) response["object_claim"]).GetArrayLength()); - Assert.Equal("value-1", ((JsonElement) response["object_claim"])[0].GetProperty("parameter_1").GetString()); - Assert.Equal("value-2", ((JsonElement) response["object_claim"])[1].GetProperty("parameter_2").GetString()); - } - - [Fact] - public async Task HandleIntrospectionRequest_RequestIsRejectedWhenReferenceTokenIsUnknown() - { - // Arrange - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) - .ReturnsAsync(value: null); - }); + [Fact] + public async Task HandleIntrospectionRequest_AuthorizationIsIgnoredWhenAuthorizationStorageIsDisabled() + { + // Arrange + var manager = CreateAuthorizationManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(new OpenIddictAuthorization()); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.Services.AddSingleton(CreateApplicationManager(mock => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetAudiences("Fabrikam") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) - .ReturnsAsync(true); - - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(true); - })); - - options.Services.AddSingleton(manager); + return default; + }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + options.Services.AddSingleton(CreateApplicationManager(mock => { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" - }); + var application = new OpenIddictApplication(); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - Mock.Get(manager).Verify(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny()), Times.AtLeastOnce()); - } + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); - [Fact] - public async Task HandleIntrospectionRequest_AuthorizationIsIgnoredWhenAuthorizationStorageIsDisabled() - { - // Arrange - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(new OpenIddictAuthorization()); - }); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateTokenManager(mock => { - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + var token = new OpenIddictToken(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetAudiences("Fabrikam") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - - options.Services.AddSingleton(CreateApplicationManager(mock => - { - var application = new OpenIddictApplication(); - - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.AccessToken); - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(true); - })); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - options.Services.AddSingleton(CreateTokenManager(mock => - { - var token = new OpenIddictToken(); + mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) + .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); - mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) - .ReturnsAsync(token); + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.AccessToken); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + })); - mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) - .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); + options.Services.AddSingleton(manager); - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + options.DisableAuthorizationStorage(); + }); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - })); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" + }); - options.Services.AddSingleton(manager); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - options.DisableAuthorizationStorage(); - }); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Never()); + } - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationCannotBeFound() + { + // Arrange + var manager = CreateAuthorizationManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(value: null); + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" - }); + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetAudiences("Fabrikam") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Never()); - } + return default; + }); - [Fact] - public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationCannotBeFound() - { - // Arrange - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(value: null); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetAudiences("Fabrikam") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - }); + var application = new OpenIddictApplication(); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - - options.Services.AddSingleton(CreateApplicationManager(mock => - { - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(true); - })); + options.Services.AddSingleton(CreateTokenManager(mock => + { + var token = new OpenIddictToken(); - options.Services.AddSingleton(CreateTokenManager(mock => - { - var token = new OpenIddictToken(); + mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) - .ReturnsAsync(token); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.AccessToken); - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.AccessToken); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) + .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); - mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) - .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + })); - mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - })); + options.Services.AddSingleton(manager); - options.Services.AddSingleton(manager); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" + }); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" - }); + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2023), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2023), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2023), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2023), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); - } + [Fact] + public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationIsInvalid() + { + // Arrange + var authorization = new OpenIddictAuthorization(); - [Fact] - public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationIsInvalid() + var manager = CreateAuthorizationManager(mock => { - // Arrange - var authorization = new OpenIddictAuthorization(); - - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) - .ReturnsAsync(authorization); + mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) + .ReturnsAsync(authorization); - mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny())) - .ReturnsAsync(false); - }); + mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny())) + .ReturnsAsync(false); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetAudiences("Fabrikam") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetAudiences("Fabrikam") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.Services.AddSingleton(CreateApplicationManager(mock => - { - var application = new OpenIddictApplication(); - - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) - .ReturnsAsync(true); - - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(true); - })); - - options.Services.AddSingleton(CreateTokenManager(mock => - { - var token = new OpenIddictToken(); - - mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) - .ReturnsAsync(token); - - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.AccessToken); - - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - - mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) - .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); - - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); - - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); - - mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) - .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); - })); - - options.Services.AddSingleton(manager); - - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + options.Services.AddSingleton(CreateApplicationManager(mock => { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" - }); + var application = new OpenIddictApplication(); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2023), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2023), response.ErrorUri); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); - [Fact] - public async Task HandleIntrospectionRequest_RequestIsRejectedWhenReferenceTokenIsInvalid() - { - // Arrange - var token = new OpenIddictToken(); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); - var manager = CreateTokenManager(mock => + options.Services.AddSingleton(CreateTokenManager(mock => { + var token = new OpenIddictToken(); + mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) .ReturnsAsync(token); @@ -1398,296 +1344,349 @@ namespace OpenIddict.Server.IntegrationTests .ReturnsAsync(token); mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(false); - }); + .ReturnsAsync(true); - await using var server = await CreateServerAsync(options => - { - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) + .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + })); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetAudiences("Fabrikam") - .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.Services.AddSingleton(manager); - return default; - }); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + await using var client = await server.CreateClientAsync(); - options.Services.AddSingleton(CreateApplicationManager(mock => - { - var application = new OpenIddictApplication(); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" + }); + + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2023), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2023), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny()), Times.Once()); + } - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + [Fact] + public async Task HandleIntrospectionRequest_RequestIsRejectedWhenReferenceTokenIsInvalid() + { + // Arrange + var token = new OpenIddictToken(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) - .ReturnsAsync(true); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(true); - })); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.AccessToken); - options.Services.AddSingleton(manager); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); - }); + mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) + .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(false); + }); + + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetAudiences("Fabrikam") + .SetAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2019), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2019), response.ErrorUri); - - Mock.Get(manager).Verify(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()), Times.Once()); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleIntrospectionRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.EnableDegradedMode(); + var application = new OpenIddictApplication(); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny())) + .ReturnsAsync(true); - return default; - }); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(true); + })); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + options.Services.AddSingleton(manager); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - return default; - })); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" + }); + + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2019), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2019), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()), Times.Once()); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleIntrospectionRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + options.AddEventHandler(builder => { - Token = "2YotnFZFEjr1zCsicMWpAA" + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); + + return default; + }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); + + return default; + })); + }); - [Fact] - public async Task HandleIntrospectionRequest_AllowsHandlingResponse() + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + [Fact] + public async Task HandleIntrospectionRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); + + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task HandleIntrospectionRequest_AllowsSkippingHandler() + [Fact] + public async Task HandleIntrospectionRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + return default; + })); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ApplyIntrospectionResponse_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + [Fact] + public async Task ApplyIntrospectionResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); + + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyIntrospectionResponse_ResponseContainsCustomParameters() + [Fact] + public async Task ApplyIntrospectionResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; + context.Response["parameter_with_multiple_values"] = new[] { - context.Response["custom_parameter"] = "custom_value"; - context.Response["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); + "custom_value_1", + "custom_value_2" + }; + + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - Token = "SlAV32hkKG" - }); + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + Token = "SlAV32hkKG" + }); - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Protection.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Protection.cs index 26c4108f..274d60c9 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Protection.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Protection.cs @@ -17,550 +17,549 @@ using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests + [Fact] + public async Task ValidateToken_IssuedAtIsMappedToCreationDate() { - [Fact] - public async Task ValidateToken_IssuedAtIsMappedToCreationDate() + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - var identity = new ClaimsIdentity("Bearer"); - identity.AddClaim(new Claim(Claims.IssuedAt, "1577836800", ClaimValueTypes.Integer64)); + var identity = new ClaimsIdentity("Bearer"); + identity.AddClaim(new Claim(Claims.IssuedAt, "1577836800", ClaimValueTypes.Integer64)); - context.Principal = new ClaimsPrincipal(identity) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + context.Principal = new ClaimsPrincipal(identity) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal(1577836800, (long) response[Claims.IssuedAt]); - Assert.Equal("Wed, 01 Jan 2020 00:00:00 GMT", (string?) response[Claims.Private.CreationDate]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateToken_ExpiresAtIsMappedToExpirationDate() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal(1577836800, (long) response[Claims.IssuedAt]); + Assert.Equal("Wed, 01 Jan 2020 00:00:00 GMT", (string?) response[Claims.Private.CreationDate]); + } - return default; - })); + [Fact] + public async Task ValidateToken_ExpiresAtIsMappedToExpirationDate() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + context.SkipRequest(); + + return default; + })); - var identity = new ClaimsIdentity("Bearer"); - identity.AddClaim(new Claim(Claims.ExpiresAt, "2524608000", ClaimValueTypes.Integer64)); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(identity) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + var identity = new ClaimsIdentity("Bearer"); + identity.AddClaim(new Claim(Claims.ExpiresAt, "2524608000", ClaimValueTypes.Integer64)); - return default; - }); + context.Principal = new ClaimsPrincipal(identity) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal(2524608000, (long) response[Claims.ExpiresAt]); - Assert.Equal("Sat, 01 Jan 2050 00:00:00 GMT", (string?) response[Claims.Private.ExpirationDate]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateToken_AuthorizedPartyIsMappedToPresenter() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal(2524608000, (long) response[Claims.ExpiresAt]); + Assert.Equal("Sat, 01 Jan 2050 00:00:00 GMT", (string?) response[Claims.Private.ExpirationDate]); + } - return default; - })); + [Fact] + public async Task ValidateToken_AuthorizedPartyIsMappedToPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + context.SkipRequest(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaim(Claims.AuthorizedParty, "Fabrikam"); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaim(Claims.AuthorizedParty, "Fabrikam"); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal("Fabrikam", (string?) response[Claims.AuthorizedParty]); - Assert.Equal("Fabrikam", (string?) response[Claims.Private.Presenter]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateToken_ClientIdIsMappedToPresenter() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal("Fabrikam", (string?) response[Claims.AuthorizedParty]); + Assert.Equal("Fabrikam", (string?) response[Claims.Private.Presenter]); + } - return default; - })); + [Fact] + public async Task ValidateToken_ClientIdIsMappedToPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + context.SkipRequest(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaim(Claims.ClientId, "Fabrikam"); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaim(Claims.ClientId, "Fabrikam"); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal("Fabrikam", (string?) response[Claims.ClientId]); - Assert.Equal("Fabrikam", (string?) response[Claims.Private.Presenter]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateToken_SinglePublicAudienceIsMappedToPrivateClaims() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal("Fabrikam", (string?) response[Claims.ClientId]); + Assert.Equal("Fabrikam", (string?) response[Claims.Private.Presenter]); + } - return default; - })); + [Fact] + public async Task ValidateToken_SinglePublicAudienceIsMappedToPrivateClaims() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + context.SkipRequest(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaim(Claims.Audience, "Fabrikam"); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaim(Claims.Audience, "Fabrikam"); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal("Fabrikam", (string?) response[Claims.Audience]); - Assert.Equal("Fabrikam", (string?) response[Claims.Private.Audience]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateToken_MultiplePublicAudiencesAreMappedToPrivateClaims() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal("Fabrikam", (string?) response[Claims.Audience]); + Assert.Equal("Fabrikam", (string?) response[Claims.Private.Audience]); + } - return default; - })); + [Fact] + public async Task ValidateToken_MultiplePublicAudiencesAreMappedToPrivateClaims() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + context.SkipRequest(); + + return default; + })); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaims(Claims.Audience, ImmutableArray.Create("Fabrikam", "Contoso")); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaims(Claims.Audience, ImmutableArray.Create("Fabrikam", "Contoso")); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Audience]); - Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Private.Audience]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateToken_MultiplePublicScopesAreNormalizedToSingleClaim() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Audience]); + Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Private.Audience]); + } - return default; - })); + [Fact] + public async Task ValidateToken_MultiplePublicScopesAreNormalizedToSingleClaim() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + context.SkipRequest(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaims(Claims.Scope, ImmutableArray.Create(Scopes.OpenId, Scopes.Profile)); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaims(Claims.Scope, ImmutableArray.Create(Scopes.OpenId, Scopes.Profile)); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal("openid profile", (string?) response[Claims.Scope]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateToken_SinglePublicScopeIsMappedToPrivateClaims() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal("openid profile", (string?) response[Claims.Scope]); + } - return default; - })); + [Fact] + public async Task ValidateToken_SinglePublicScopeIsMappedToPrivateClaims() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + context.SkipRequest(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaim(Claims.Scope, "openid profile"); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaim(Claims.Scope, "openid profile"); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, (string[]?) response[Claims.Private.Scope]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateToken_MultiplePublicScopesAreMappedToPrivateClaims() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, (string[]?) response[Claims.Private.Scope]); + } - return default; - })); + [Fact] + public async Task ValidateToken_MultiplePublicScopesAreMappedToPrivateClaims() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + context.SkipRequest(); + + return default; + })); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaims(Claims.Scope, ImmutableArray.Create(Scopes.OpenId, Scopes.Profile)); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaims(Claims.Scope, ImmutableArray.Create(Scopes.OpenId, Scopes.Profile)); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" - }); + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest + { + AccessToken = "access_token" + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, (string[]?) response[Claims.Private.Scope]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal(new[] { Scopes.OpenId, Scopes.Profile }, (string[]?) response[Claims.Private.Scope]); + } - [Fact] - public async Task ValidateToken_MissingTokenTypeThrowsAnException() + [Fact] + public async Task ValidateToken_MissingTokenTypeThrowsAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(Array.Empty(), context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(null) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + Assert.Equal("access_token", context.Token); + Assert.Equal(Array.Empty(), context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(null) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/connect/introspect", new OpenIddictRequest { - return client.GetAsync("/connect/introspect", new OpenIddictRequest - { - Token = "access_token" - }); + Token = "access_token" }); + }); - // Assert - Assert.Equal(SR.GetResourceString(SR.ID0004), exception.Message); - } + // Assert + Assert.Equal(SR.GetResourceString(SR.ID0004), exception.Message); + } - [Fact] - public async Task ValidateToken_InvalidTokenTypeThrowsAnException() + [Fact] + public async Task ValidateToken_InvalidTokenTypeThrowsAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/authenticate", new OpenIddictRequest { - return client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" - }); + AccessToken = "access_token" }); + }); - // Assert - Assert.Equal(SR.FormatID0005(TokenTypeHints.AuthorizationCode, TokenTypeHints.AccessToken), exception.Message); - } + // Assert + Assert.Equal(SR.FormatID0005(TokenTypeHints.AuthorizationCode, TokenTypeHints.AccessToken), exception.Message); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs index 00b3c483..e58a0d59 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs @@ -19,1050 +19,1049 @@ using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using static OpenIddict.Server.OpenIddictServerHandlers.Revocation; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Get))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractRevocationRequest_UnexpectedMethodReturnsAnError(string method) { - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Get))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractRevocationRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/connect/revoke", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractRevocationRequest_AllowsRejectingRequest(string error, string description, string uri) + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.SendAsync(method, "/connect/revoke", new OpenIddictRequest()); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractRevocationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest()); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ExtractRevocationRequest_AllowsHandlingResponse() + [Fact] + public async Task ExtractRevocationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractRevocationRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractRevocationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } + + [Fact] + public async Task ValidateRevocationRequest_MissingTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateRevocationRequest_MissingTokenCausesAnError() + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + Token = null + }); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = null - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.Token), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.Token), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } - - [Theory] - [InlineData(TokenTypeHints.AuthorizationCode)] - [InlineData(TokenTypeHints.DeviceCode)] - [InlineData(TokenTypeHints.IdToken)] - [InlineData(TokenTypeHints.UserCode)] - [InlineData("custom_token")] - public async Task ValidateRevocationRequest_UnsupportedTokenTypeCausesAnError(string type) + [Theory] + [InlineData(TokenTypeHints.AuthorizationCode)] + [InlineData(TokenTypeHints.DeviceCode)] + [InlineData(TokenTypeHints.IdToken)] + [InlineData(TokenTypeHints.UserCode)] + [InlineData("custom_token")] + public async Task ValidateRevocationRequest_UnsupportedTokenTypeCausesAnError(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("5HtRgAtc02", context.Token); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(type); + Assert.Equal("5HtRgAtc02", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(type); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - Token = "5HtRgAtc02" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.UnsupportedTokenType, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2079), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2079), response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + ClientId = "Fabrikam", + Token = "5HtRgAtc02" + }); + + // Assert + Assert.Equal(Errors.UnsupportedTokenType, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2079), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2079), response.ErrorUri); + } - [Fact] - public async Task ValidateRevocationRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter() + [Fact] + public async Task ValidateRevocationRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetAudiences("AdventureWorks") - .SetPresenters("Contoso"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetAudiences("AdventureWorks") + .SetPresenters("Contoso"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - Token = "2YotnFZFEjr1zCsicMWpAA", - TokenTypeHint = TokenTypeHints.AccessToken - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + ClientId = "Fabrikam", + Token = "2YotnFZFEjr1zCsicMWpAA", + TokenTypeHint = TokenTypeHints.AccessToken + }); + + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri); + } - [Fact] - public async Task ValidateRevocationRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter() + [Fact] + public async Task ValidateRevocationRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal("8xLOxBtZp8", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetPresenters("Contoso"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetPresenters("Contoso"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); + }); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - Token = "8xLOxBtZp8", - TokenTypeHint = TokenTypeHints.RefreshToken - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + ClientId = "Fabrikam", + Token = "8xLOxBtZp8", + TokenTypeHint = TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri); + } - [Fact] - public async Task ValidateRevocationRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() + [Fact] + public async Task ValidateRevocationRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() + { + // Arrange + await using var server = await CreateServerAsync(builder => { - // Arrange - await using var server = await CreateServerAsync(builder => - { - builder.Configure(options => options.AcceptAnonymousClients = false); - }); + builder.Configure(options => options.AcceptAnonymousClients = false); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "SlAV32hkKG", - TokenTypeHint = TokenTypeHints.RefreshToken - }); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + Token = "SlAV32hkKG", + TokenTypeHint = TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); + } - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri); - } + [Fact] + public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCannotBeFound() + { + // Arrange + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(value: null); + }); - [Fact] - public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCannotBeFound() + await using var server = await CreateServerAsync(builder => { - // Arrange - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(value: null); - }); + builder.Services.AddSingleton(manager); + }); - await using var server = await CreateServerAsync(builder => - { - builder.Services.AddSingleton(manager); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + ClientId = "Fabrikam", + Token = "SlAV32hkKG", + TokenTypeHint = TokenTypeHints.RefreshToken + }); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - Token = "SlAV32hkKG", - TokenTypeHint = TokenTypeHints.RefreshToken - }); + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + } - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - } + [Fact] + public async Task ValidateRevocationRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + { + // Arrange + var application = new OpenIddictApplication(); - [Fact] - public async Task ValidateRevocationRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() + var manager = CreateApplicationManager(mock => { - // Arrange - var application = new OpenIddictApplication(); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Revocation, It.IsAny())) + .ReturnsAsync(false); + }); - mock.Setup(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Revocation, It.IsAny())) - .ReturnsAsync(false); - }); + await using var server = await CreateServerAsync(builder => + { + builder.Services.AddSingleton(manager); - await using var server = await CreateServerAsync(builder => - { - builder.Services.AddSingleton(manager); + builder.Configure(options => options.IgnoreEndpointPermissions = false); + }); - builder.Configure(options => options.IgnoreEndpointPermissions = false); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + ClientId = "Fabrikam", + Token = "SlAV32hkKG", + TokenTypeHint = TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Equal(Errors.UnauthorizedClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2078), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2078), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, + Permissions.Endpoints.Revocation, It.IsAny()), Times.Once()); + } - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - Token = "SlAV32hkKG", - TokenTypeHint = TokenTypeHints.RefreshToken - }); + [Fact] + public async Task ValidateRevocationRequest_ClientSecretCannotBeUsedByPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); - // Assert - Assert.Equal(Errors.UnauthorizedClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2078), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2078), response.ErrorUri); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, - Permissions.Endpoints.Revocation, It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + }); - [Fact] - public async Task ValidateRevocationRequest_ClientSecretCannotBeUsedByPublicClients() + await using var server = await CreateServerAsync(builder => { - // Arrange - var application = new OpenIddictApplication(); + builder.Services.AddSingleton(manager); + }); - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - }); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "SlAV32hkKG", + TokenTypeHint = TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } - await using var server = await CreateServerAsync(builder => - { - builder.Services.AddSingleton(manager); - }); + [Fact] + public async Task ValidateRevocationRequest_ClientSecretIsRequiredForNonPublicClients() + { + // Arrange + var application = new OpenIddictApplication(); - await using var client = await server.CreateClientAsync(); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "SlAV32hkKG", - TokenTypeHint = TokenTypeHints.RefreshToken - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); + }); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri); + await using var server = await CreateServerAsync(builder => + { + builder.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateRevocationRequest_ClientSecretIsRequiredForNonPublicClients() + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { - // Arrange - var application = new OpenIddictApplication(); + ClientId = "Fabrikam", + ClientSecret = null, + Token = "SlAV32hkKG", + TokenTypeHint = TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); + } - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + [Fact] + public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() + { + // Arrange + var application = new OpenIddictApplication(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(false); - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - await using var server = await CreateServerAsync(builder => - { - builder.Services.AddSingleton(manager); - }); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(false); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) + .ReturnsAsync(false); + }); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = null, - Token = "SlAV32hkKG", - TokenTypeHint = TokenTypeHints.RefreshToken - }); + await using var server = await CreateServerAsync(builder => + { + builder.Services.AddSingleton(manager); + }); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri); + await using var client = await server.CreateClientAsync(); - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.Once()); - } + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "SlAV32hkKG", + TokenTypeHint = TokenTypeHints.RefreshToken + }); + + // Assert + Assert.Equal(Errors.InvalidClient, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2055), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2055), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); + } - [Fact] - public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateRevocationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - var application = new OpenIddictApplication(); + options.EnableDegradedMode(); - var manager = CreateApplicationManager(mock => + options.AddEventHandler(builder => { - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); - - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(false); - - mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) - .ReturnsAsync(false); - }); + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - await using var server = await CreateServerAsync(builder => - { - builder.Services.AddSingleton(manager); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "SlAV32hkKG", - TokenTypeHint = TokenTypeHints.RefreshToken + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - // Assert - Assert.Equal(Errors.InvalidClient, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2055), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2055), response.ErrorUri); - - Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateRevocationRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + context.Reject(error, description, uri); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + return default; + })); + }); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + await using var client = await server.CreateClientAsync(); - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ValidateRevocationRequest_AllowsHandlingResponse() + [Fact] + public async Task ValidateRevocationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ValidateRevocationRequest_AllowsSkippingHandler() + [Fact] + public async Task ValidateRevocationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + return default; + })); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleRevocationRequest_TokenIsNotRevokedWhenItIsUnknown() + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { - // Arrange - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(value: null); - }); + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } + + [Fact] + public async Task HandleRevocationRequest_TokenIsNotRevokedWhenItIsUnknown() + { + // Arrange + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(value: null); + }); - await using var server = await CreateServerAsync(options => + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + Assert.Equal("SlAV32hkKG", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); - - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "SlAV32hkKG" - }); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2003), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2003), response.ErrorUri); + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny(), It.IsAny()), Times.Never()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleRevocationRequest_TokenIsNotRevokedWhenItIsAlreadyRevoked() + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { - // Arrange - var token = new OpenIddictToken(); + Token = "SlAV32hkKG" + }); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2003), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2003), response.ErrorUri); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .Returns(new ValueTask("3E228451-1555-46F7-A471-951EFBA23A56")); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny(), It.IsAny()), Times.Never()); + } - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(false); + [Fact] + public async Task HandleRevocationRequest_TokenIsNotRevokedWhenItIsAlreadyRevoked() + { + // Arrange + var token = new OpenIddictToken(); - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.RefreshToken); - }); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - await using var server = await CreateServerAsync(options => + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .Returns(new ValueTask("3E228451-1555-46F7-A471-951EFBA23A56")); + + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(false); + + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.RefreshToken); + }); + + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); + Assert.Equal("SlAV32hkKG", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); - - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "SlAV32hkKG" - }); - - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); + options.Services.AddSingleton(manager); + }); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny(), It.IsAny()), Times.Never()); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleRevocationRequest_TokenIsSuccessfullyRevoked() + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { - // Arrange - var token = new OpenIddictToken(); + Token = "SlAV32hkKG" + }); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .Returns(new ValueTask("3E228451-1555-46F7-A471-951EFBA23A56")); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny(), It.IsAny()), Times.Never()); + } - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + [Fact] + public async Task HandleRevocationRequest_TokenIsSuccessfullyRevoked() + { + // Arrange + var token = new OpenIddictToken(); - mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) - .ReturnsAsync(TokenTypeHints.RefreshToken); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.TryRevokeAsync(token, It.IsAny())) - .ReturnsAsync(true); - }); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .Returns(new ValueTask("3E228451-1555-46F7-A471-951EFBA23A56")); + + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); + + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.RefreshToken); - await using var server = await CreateServerAsync(options => + mock.Setup(manager => manager.TryRevokeAsync(token, It.IsAny())) + .ReturnsAsync(true); + }); + + await using var server = await CreateServerAsync(options => + { + options.AddEventHandler(builder => { - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + Assert.Equal("SlAV32hkKG", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); - - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "SlAV32hkKG" - }); + options.Services.AddSingleton(manager); + }); - // Assert - Assert.Empty(response.GetParameters()); - - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(token, It.IsAny()), Times.Once()); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleRevocationRequest_AllowsRejectingRequest(string error, string description, string uri) + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + Token = "SlAV32hkKG" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + // Assert + Assert.Empty(response.GetParameters()); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(token, It.IsAny()), Times.Once()); + } - return default; - }); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleRevocationRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); + + return default; + }); - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + return default; + })); + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleRevocationRequest_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + [Fact] + public async Task HandleRevocationRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task HandleRevocationRequest_AllowsSkippingHandler() + [Fact] + public async Task HandleRevocationRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + return default; + })); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ApplyRevocationResponse_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + [Fact] + public async Task ApplyRevocationResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); + + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyRevocationResponse_ResponseContainsCustomParameters() + [Fact] + public async Task ApplyRevocationResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; + context.Response["parameter_with_multiple_values"] = new[] { - context.Response["custom_parameter"] = "custom_value"; - context.Response["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); + "custom_value_1", + "custom_value_2" + }; - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - Token = "2YotnFZFEjr1zCsicMWpAA" - }); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + Token = "2YotnFZFEjr1zCsicMWpAA" + }); - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs index 3e840aad..ca6515ae 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs @@ -16,756 +16,755 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractLogoutRequest_UnexpectedMethodReturnsAnError(string method) { - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractLogoutRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/connect/logout", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractLogoutRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.SendAsync(method, "/connect/logout", new OpenIddictRequest()); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractLogoutRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ExtractLogoutRequest_AllowsHandlingResponse() + [Fact] + public async Task ExtractLogoutRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/connect/logout"); + // Act + var response = await client.GetAsync("/connect/logout"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractLogoutRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractLogoutRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.GetAsync("/connect/logout"); - - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } - - [Theory] - [InlineData("/path", SR.ID2030)] - [InlineData("/tmp/file.xml", SR.ID2030)] - [InlineData("C:\\tmp\\file.xml", SR.ID2030)] - [InlineData("http://www.fabrikam.com/path#param=value", SR.ID2031)] - public async Task ValidateLogoutRequest_InvalidRedirectUriCausesAnError(string address, string message) - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - PostLogoutRedirectUri = address - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(string.Format(SR.GetResourceString(message), Parameters.PostLogoutRedirectUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(message), response.ErrorUri); - } - - [Fact] - public async Task ValidateLogoutRequest_RequestIsRejectedWhenNoMatchingApplicationIsFound() - { - // Arrange - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny())) - .Returns(AsyncEnumerable.Empty()); - }); - - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - PostLogoutRedirectUri = "http://www.fabrikam.com/path" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.PostLogoutRedirectUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - - Mock.Get(manager).Verify(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()), Times.Once()); - } - - [Fact] - public async Task ValidateLogoutRequest_RequestIsRejectedWhenNoMatchingApplicationIsGrantedEndpointPermission() - { - // Arrange - var applications = new[] - { - new OpenIddictApplication(), - new OpenIddictApplication() - }; - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny())) - .Returns(applications.ToAsyncEnumerable()); - - mock.Setup(manager => manager.HasPermissionAsync(applications[0], Permissions.Endpoints.Logout, It.IsAny())) - .ReturnsAsync(false); - - mock.Setup(manager => manager.HasPermissionAsync(applications[1], Permissions.Endpoints.Logout, It.IsAny())) - .ReturnsAsync(false); - }); - - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - - options.Configure(options => options.IgnoreEndpointPermissions = false); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - PostLogoutRedirectUri = "http://www.fabrikam.com/path" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.PostLogoutRedirectUri), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - - Mock.Get(manager).Verify(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[0], Permissions.Endpoints.Logout, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[1], Permissions.Endpoints.Logout, It.IsAny()), Times.Once()); - } - - [Fact] - public async Task ValidateLogoutRequest_RequestIsValidatedWhenMatchingApplicationIsFound() - { - // Arrange - var applications = new[] - { - new OpenIddictApplication(), - new OpenIddictApplication(), - new OpenIddictApplication() - }; - - var manager = CreateApplicationManager(mock => - { - mock.Setup(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny())) - .Returns(applications.ToAsyncEnumerable()); - - mock.Setup(manager => manager.HasPermissionAsync(applications[0], Permissions.Endpoints.Logout, It.IsAny())) - .ReturnsAsync(false); - - mock.Setup(manager => manager.HasPermissionAsync(applications[1], Permissions.Endpoints.Logout, It.IsAny())) - .ReturnsAsync(true); - - mock.Setup(manager => manager.HasPermissionAsync(applications[2], Permissions.Endpoints.Logout, It.IsAny())) - .ReturnsAsync(false); - }); - - await using var server = await CreateServerAsync(options => - { - options.Services.AddSingleton(manager); - - options.SetLogoutEndpointUris("/signout"); - options.Configure(options => options.IgnoreEndpointPermissions = false); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SignOut(); - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/signout", new OpenIddictRequest - { - PostLogoutRedirectUri = "http://www.fabrikam.com/path", - State = "af0ifjsldkj" - }); - - // Assert - Assert.Equal("af0ifjsldkj", response.State); - - Mock.Get(manager).Verify(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[0], Permissions.Endpoints.Logout, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[1], Permissions.Endpoints.Logout, It.IsAny()), Times.Once()); - Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[2], Permissions.Endpoints.Logout, It.IsAny()), Times.Never()); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateLogoutRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); - return default; - })); - }); + // Act + var response = await client.GetAsync("/connect/logout"); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + [Theory] + [InlineData("/path", SR.ID2030)] + [InlineData("/tmp/file.xml", SR.ID2030)] + [InlineData("C:\\tmp\\file.xml", SR.ID2030)] + [InlineData("http://www.fabrikam.com/path#param=value", SR.ID2031)] + public async Task ValidateLogoutRequest_InvalidRedirectUriCausesAnError(string address, string message) + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest + { + PostLogoutRedirectUri = address + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(string.Format(SR.GetResourceString(message), Parameters.PostLogoutRedirectUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(message), response.ErrorUri); + } - [Fact] - public async Task ValidateLogoutRequest_AllowsHandlingResponse() + [Fact] + public async Task ValidateLogoutRequest_RequestIsRejectedWhenNoMatchingApplicationIsFound() + { + // Arrange + var manager = CreateApplicationManager(mock => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + mock.Setup(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny())) + .Returns(AsyncEnumerable.Empty()); + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); + }); - context.HandleRequest(); + await using var client = await server.CreateClientAsync(); - return default; - })); - }); + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest + { + PostLogoutRedirectUri = "http://www.fabrikam.com/path" + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.PostLogoutRedirectUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + Mock.Get(manager).Verify(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()), Times.Once()); + } - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + [Fact] + public async Task ValidateLogoutRequest_RequestIsRejectedWhenNoMatchingApplicationIsGrantedEndpointPermission() + { + // Arrange + var applications = new[] + { + new OpenIddictApplication(), + new OpenIddictApplication() + }; - [Fact] - public async Task ValidateLogoutRequest_AllowsSkippingHandler() + var manager = CreateApplicationManager(mock => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + mock.Setup(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny())) + .Returns(applications.ToAsyncEnumerable()); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); - - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleLogoutRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + mock.Setup(manager => manager.HasPermissionAsync(applications[0], Permissions.Endpoints.Logout, It.IsAny())) + .ReturnsAsync(false); - return default; - })); - }); + mock.Setup(manager => manager.HasPermissionAsync(applications[1], Permissions.Endpoints.Logout, It.IsAny())) + .ReturnsAsync(false); + }); - await using var client = await server.CreateClientAsync(); + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(manager); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + options.Configure(options => options.IgnoreEndpointPermissions = false); + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleLogoutRequest_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + PostLogoutRedirectUri = "http://www.fabrikam.com/path" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.PostLogoutRedirectUri), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri); + + Mock.Get(manager).Verify(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[0], Permissions.Endpoints.Logout, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[1], Permissions.Endpoints.Logout, It.IsAny()), Times.Once()); + } - context.HandleRequest(); + [Fact] + public async Task ValidateLogoutRequest_RequestIsValidatedWhenMatchingApplicationIsFound() + { + // Arrange + var applications = new[] + { + new OpenIddictApplication(), + new OpenIddictApplication(), + new OpenIddictApplication() + }; - return default; - })); - }); + var manager = CreateApplicationManager(mock => + { + mock.Setup(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny())) + .Returns(applications.ToAsyncEnumerable()); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasPermissionAsync(applications[0], Permissions.Endpoints.Logout, It.IsAny())) + .ReturnsAsync(false); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + mock.Setup(manager => manager.HasPermissionAsync(applications[1], Permissions.Endpoints.Logout, It.IsAny())) + .ReturnsAsync(true); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + mock.Setup(manager => manager.HasPermissionAsync(applications[2], Permissions.Endpoints.Logout, It.IsAny())) + .ReturnsAsync(false); + }); - [Fact] - public async Task HandleLogoutRequest_AllowsSkippingHandler() + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.Services.AddSingleton(manager); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.SetLogoutEndpointUris("/signout"); + options.Configure(options => options.IgnoreEndpointPermissions = false); - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Act + var response = await client.PostAsync("/signout", new OpenIddictRequest + { + PostLogoutRedirectUri = "http://www.fabrikam.com/path", + State = "af0ifjsldkj" + }); - [Fact] - public async Task HandleLogoutResponse_ResponseContainsCustomParameters() + // Assert + Assert.Equal("af0ifjsldkj", response.State); + + Mock.Get(manager).Verify(manager => manager.FindByPostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[0], Permissions.Endpoints.Logout, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[1], Permissions.Endpoints.Logout, It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(applications[2], Permissions.Endpoints.Logout, It.IsAny()), Times.Never()); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateLogoutRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SignOut(); - - context.Parameters["custom_parameter"] = "custom_value"; - context.Parameters["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - PostLogoutRedirectUri = "http://www.fabrikam.com/path" - }); - - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } - - [Fact] - public async Task ApplyLogoutResponse_AllowsHandlingResponse() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SignOut(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); - return default; - })); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } + + [Fact] + public async Task ValidateLogoutRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyLogoutResponse_ResponseContainsCustomParameters() + [Fact] + public async Task ValidateLogoutRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SignOut(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Response["custom_parameter"] = "custom_value"; - context.Response["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - PostLogoutRedirectUri = "http://www.fabrikam.com/path" - }); - - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } - - [Fact] - public async Task ApplyLogoutResponse_UsesPostLogoutRedirectUriWhenProvided() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SignOut(); + await using var client = await server.CreateClientAsync(); - return default; - })); + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Response["target_uri"] = context.PostLogoutRedirectUri; + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } + + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleLogoutRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - PostLogoutRedirectUri = "http://www.fabrikam.com/path" - }); + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); - // Assert - Assert.Equal("http://www.fabrikam.com/path", (string?) response["target_uri"]); - } + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - [Fact] - public async Task ApplyLogoutResponse_ReturnsEmptyResponseWhenNoPostLogoutRedirectUriIsProvided() + [Fact] + public async Task HandleLogoutRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.SignOut(); + name = "Bob le Bricoleur" + }); - return default; - })); + context.HandleRequest(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Response["target_uri"] = context.PostLogoutRedirectUri; + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - return default; - })); - }); + [Fact] + public async Task HandleLogoutRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Empty(response.GetParameters()); - } + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - [Fact] - public async Task ApplyLogoutResponse_DoesNotSetStateWhenUserIsNotRedirected() + [Fact] + public async Task HandleLogoutResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetLogoutEndpointUris("/signout"); + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + context.Parameters["custom_parameter"] = "custom_value"; + context.Parameters["parameter_with_multiple_values"] = new[] { - context.SignOut(); + "custom_value_1", + "custom_value_2" + }; - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/signout", new OpenIddictRequest - { - State = "af0ifjsldkj" - }); + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest + { + PostLogoutRedirectUri = "http://www.fabrikam.com/path" + }); - // Assert - Assert.Null(response.State); - } + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); + } - [Fact] - public async Task ApplyLogoutResponse_FlowsStateWhenRedirectUriIsUsed() + [Fact] + public async Task ApplyLogoutResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetLogoutEndpointUris("/signout"); + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); + + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.SignOut(); + name = "Bob le Bricoleur" + }); - return default; - })); - }); + context.HandleRequest(); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync("/signout", new OpenIddictRequest - { - PostLogoutRedirectUri = "http://www.fabrikam.com/path", - State = "af0ifjsldkj" - }); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal("af0ifjsldkj", response.State); - } + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyLogoutResponse_DoesNotOverrideStateSetByApplicationCode() + [Fact] + public async Task ApplyLogoutResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetLogoutEndpointUris("/signout"); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SignOut(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; + context.Response["parameter_with_multiple_values"] = new[] { - context.Response.State = "custom_state"; + "custom_value_1", + "custom_value_2" + }; + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest + { + PostLogoutRedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); + } + + [Fact] + public async Task ApplyLogoutResponse_UsesPostLogoutRedirectUriWhenProvided() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); + + return default; + })); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["target_uri"] = context.PostLogoutRedirectUri; + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest + { + PostLogoutRedirectUri = "http://www.fabrikam.com/path" + }); + + // Assert + Assert.Equal("http://www.fabrikam.com/path", (string?) response["target_uri"]); + } + + [Fact] + public async Task ApplyLogoutResponse_ReturnsEmptyResponseWhenNoPostLogoutRedirectUriIsProvided() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); + + return default; + })); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["target_uri"] = context.PostLogoutRedirectUri; + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + + // Assert + Assert.Empty(response.GetParameters()); + } + + [Fact] + public async Task ApplyLogoutResponse_DoesNotSetStateWhenUserIsNotRedirected() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/signout"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/signout", new OpenIddictRequest + { + State = "af0ifjsldkj" + }); + + // Assert + Assert.Null(response.State); + } + + [Fact] + public async Task ApplyLogoutResponse_FlowsStateWhenRedirectUriIsUsed() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/signout"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/signout", new OpenIddictRequest + { + PostLogoutRedirectUri = "http://www.fabrikam.com/path", + State = "af0ifjsldkj" + }); - return default; - })); - }); + // Assert + Assert.Equal("af0ifjsldkj", response.State); + } + + [Fact] + public async Task ApplyLogoutResponse_DoesNotOverrideStateSetByApplicationCode() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/signout"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); - await using var client = await server.CreateClientAsync(); + return default; + })); - // Act - var response = await client.PostAsync("/signout", new OpenIddictRequest - { - PostLogoutRedirectUri = "http://www.fabrikam.com/path", - State = "af0ifjsldkj" - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response.State = "custom_state"; + + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/signout", new OpenIddictRequest + { + PostLogoutRedirectUri = "http://www.fabrikam.com/path", + State = "af0ifjsldkj" + }); - // Assert - Assert.Equal("custom_state", response.State); - } + // Assert + Assert.Equal("custom_state", response.State); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Userinfo.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Userinfo.cs index 33a50e0e..e816f907 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Userinfo.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Userinfo.cs @@ -17,804 +17,803 @@ using static OpenIddict.Server.OpenIddictServerHandlers; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests + [Theory] + [InlineData(nameof(HttpMethod.Delete))] + [InlineData(nameof(HttpMethod.Head))] + [InlineData(nameof(HttpMethod.Options))] + [InlineData(nameof(HttpMethod.Put))] + [InlineData(nameof(HttpMethod.Trace))] + public async Task ExtractUserinfoRequest_UnexpectedMethodReturnsAnError(string method) { - [Theory] - [InlineData(nameof(HttpMethod.Delete))] - [InlineData(nameof(HttpMethod.Head))] - [InlineData(nameof(HttpMethod.Options))] - [InlineData(nameof(HttpMethod.Put))] - [InlineData(nameof(HttpMethod.Trace))] - public async Task ExtractUserinfoRequest_UnexpectedMethodReturnsAnError(string method) - { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.SendAsync(method, "/connect/userinfo", new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ExtractUserinfoRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + // Act + var response = await client.SendAsync(method, "/connect/userinfo", new OpenIddictRequest()); - return default; - })); - }); + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2084), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2084), response.ErrorUri); + } - await using var client = await server.CreateClientAsync(); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ExtractUserinfoRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest()); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + return default; + })); + }); - [Fact] - public async Task ExtractUserinfoRequest_AllowsHandlingResponse() + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest()); + + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } + + [Fact] + public async Task ExtractUserinfoRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/connect/userinfo"); + // Act + var response = await client.GetAsync("/connect/userinfo"); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ExtractUserinfoRequest_AllowsSkippingHandler() + [Fact] + public async Task ExtractUserinfoRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.GetAsync("/connect/userinfo"); - // Act - var response = await client.GetAsync("/connect/userinfo"); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + [Fact] + public async Task ValidateUserinfoRequest_MissingTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateUserinfoRequest_MissingTokenCausesAnError() + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); + AccessToken = null + }); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = null - }); + // Assert + Assert.Empty(response.GetParameters()); + } - // Assert - Assert.Empty(response.GetParameters()); - } + [Fact] + public async Task ValidateUserinfoRequest_InvalidTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateUserinfoRequest_InvalidTokenCausesAnError() + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + AccessToken = "SlAV32hkKG" + }); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri); - } + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri); + } - [Fact] - public async Task ValidateUserinfoRequest_ExpiredTokenCausesAnError() + [Fact] + public async Task ValidateUserinfoRequest_ExpiredTokenCausesAnError() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + Assert.Equal("SlAV32hkKG", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); - // Assert - Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2019), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2019), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ValidateUserinfoRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Assert + Assert.Equal(Errors.InvalidToken, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2019), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2019), response.ErrorUri); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ValidateUserinfoRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SlAV32hkKG", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + return default; + })); + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ValidateUserinfoRequest_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + AccessToken = "SlAV32hkKG" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } + + [Fact] + public async Task ValidateUserinfoRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SlAV32hkKG", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ValidateUserinfoRequest_AllowsSkippingHandler() + [Fact] + public async Task ValidateUserinfoRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + Assert.Equal("SlAV32hkKG", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + return default; + })); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleUserinfoRequest_BasicClaimsAreCorrectlyReturned() + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + AccessToken = "SlAV32hkKG" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetPresenters("Fabrikam", "Contoso") - .SetClaim(Claims.Subject, "Bob le Magnifique"); + [Fact] + public async Task HandleUserinfoRequest_BasicClaimsAreCorrectlyReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SlAV32hkKG", context.Token); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetPresenters("Fabrikam", "Contoso") + .SetClaim(Claims.Subject, "Bob le Magnifique"); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(3, response.Count); - Assert.Equal("http://localhost/", (string?) response[Claims.Issuer]); - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Audience]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleUserinfoRequest_NonBasicClaimsAreNotReturnedWhenNoScopeWasGranted() + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + AccessToken = "SlAV32hkKG" + }); + + // Assert + Assert.Equal(3, response.Count); + Assert.Equal("http://localhost/", (string?) response[Claims.Issuer]); + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal(new[] { "Fabrikam", "Contoso" }, (string[]?) response[Claims.Audience]); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); - - var identity = new ClaimsIdentity("Bearer"); - identity.AddClaim(Claims.Subject, "Bob le Magnifique"); - identity.AddClaim(Claims.GivenName, "Bob"); - identity.AddClaim(Claims.FamilyName, "Saint-Clar"); - identity.AddClaim(Claims.Birthdate, "04/09/1933"); - identity.AddClaim(Claims.Email, "bob@le-magnifique.com"); - identity.AddClaim(Claims.PhoneNumber, "0148962355"); - - context.Principal = new ClaimsPrincipal(identity) - .SetTokenType(TokenTypeHints.AccessToken) - .SetPresenters("Fabrikam") - .SetScopes(ImmutableArray.Create()); - - return default; - }); + [Fact] + public async Task HandleUserinfoRequest_NonBasicClaimsAreNotReturnedWhenNoScopeWasGranted() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SlAV32hkKG", context.Token); + + var identity = new ClaimsIdentity("Bearer"); + identity.AddClaim(Claims.Subject, "Bob le Magnifique"); + identity.AddClaim(Claims.GivenName, "Bob"); + identity.AddClaim(Claims.FamilyName, "Saint-Clar"); + identity.AddClaim(Claims.Birthdate, "04/09/1933"); + identity.AddClaim(Claims.Email, "bob@le-magnifique.com"); + identity.AddClaim(Claims.PhoneNumber, "0148962355"); + + context.Principal = new ClaimsPrincipal(identity) + .SetTokenType(TokenTypeHints.AccessToken) + .SetPresenters("Fabrikam") + .SetScopes(ImmutableArray.Create()); + + return default; }); - }); - - await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal(3, response.Count); - Assert.Equal("http://localhost/", (string?) response[Claims.Issuer]); - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - Assert.Equal("Fabrikam", (string?) response[Claims.Audience]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleUserinfoRequest_ProfileClaimsAreCorrectlyReturned() + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + AccessToken = "SlAV32hkKG" + }); + + // Assert + Assert.Equal(3, response.Count); + Assert.Equal("http://localhost/", (string?) response[Claims.Issuer]); + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + Assert.Equal("Fabrikam", (string?) response[Claims.Audience]); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + [Fact] + public async Task HandleUserinfoRequest_ProfileClaimsAreCorrectlyReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - var identity = new ClaimsIdentity("Bearer"); - identity.AddClaim(Claims.Subject, "Bob le Magnifique"); - identity.AddClaim(Claims.GivenName, "Bob"); - identity.AddClaim(Claims.FamilyName, "Saint-Clar"); - identity.AddClaim(Claims.Birthdate, "04/09/1933"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SlAV32hkKG", context.Token); - context.Principal = new ClaimsPrincipal(identity) - .SetTokenType(TokenTypeHints.AccessToken) - .SetPresenters("Fabrikam") - .SetScopes(Scopes.Profile) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + var identity = new ClaimsIdentity("Bearer"); + identity.AddClaim(Claims.Subject, "Bob le Magnifique"); + identity.AddClaim(Claims.GivenName, "Bob"); + identity.AddClaim(Claims.FamilyName, "Saint-Clar"); + identity.AddClaim(Claims.Birthdate, "04/09/1933"); - return default; - }); + context.Principal = new ClaimsPrincipal(identity) + .SetTokenType(TokenTypeHints.AccessToken) + .SetPresenters("Fabrikam") + .SetScopes(Scopes.Profile) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); - // Assert - Assert.Equal("Bob", (string?) response[Claims.GivenName]); - Assert.Equal("Saint-Clar", (string?) response[Claims.FamilyName]); - Assert.Equal("04/09/1933", (string?) response[Claims.Birthdate]); - } + // Assert + Assert.Equal("Bob", (string?) response[Claims.GivenName]); + Assert.Equal("Saint-Clar", (string?) response[Claims.FamilyName]); + Assert.Equal("04/09/1933", (string?) response[Claims.Birthdate]); + } - [Fact] - public async Task HandleUserinfoRequest_EmailClaimIsCorrectlyReturned() + [Fact] + public async Task HandleUserinfoRequest_EmailClaimIsCorrectlyReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + Assert.Equal("SlAV32hkKG", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetPresenters("Fabrikam") - .SetScopes(Scopes.Email) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaim(Claims.Email, "bob@le-magnifique.com"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetPresenters("Fabrikam") + .SetScopes(Scopes.Email) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaim(Claims.Email, "bob@le-magnifique.com"); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); - // Assert - Assert.Equal("bob@le-magnifique.com", (string?) response[Claims.Email]); - } + // Assert + Assert.Equal("bob@le-magnifique.com", (string?) response[Claims.Email]); + } - [Fact] - public async Task HandleUserinfoRequest_PhoneClaimIsCorrectlyReturned() + [Fact] + public async Task HandleUserinfoRequest_PhoneClaimIsCorrectlyReturned() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetPresenters("Fabrikam") - .SetScopes(Scopes.Phone) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetClaim(Claims.PhoneNumber, "0148962355"); + Assert.Equal("SlAV32hkKG", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetPresenters("Fabrikam") + .SetScopes(Scopes.Phone) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetClaim(Claims.PhoneNumber, "0148962355"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); - // Assert - Assert.Equal("0148962355", (string?) response[Claims.PhoneNumber]); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task HandleUserinfoRequest_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + // Assert + Assert.Equal("0148962355", (string?) response[Claims.PhoneNumber]); + } - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task HandleUserinfoRequest_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SlAV32hkKG", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + return default; + })); + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task HandleUserinfoRequest_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + AccessToken = "SlAV32hkKG" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + [Fact] + public async Task HandleUserinfoRequest_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SlAV32hkKG", context.Token); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); + + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task HandleUserinfoRequest_AllowsSkippingHandler() + [Fact] + public async Task HandleUserinfoRequest_AllowsSkippingHandler() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + Assert.Equal("SlAV32hkKG", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); - - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + return default; + })); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ApplyUserinfoResponse_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + AccessToken = "SlAV32hkKG" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } + + [Fact] + public async Task ApplyUserinfoResponse_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SlAV32hkKG", context.Token); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + name = "Bob le Bricoleur" + }); - context.HandleRequest(); + context.HandleRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - [Fact] - public async Task ApplyUserinfoResponse_ResponseContainsCustomParameters() + [Fact] + public async Task ApplyUserinfoResponse_ResponseContainsCustomParameters() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + Assert.Equal("SlAV32hkKG", context.Token); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Response["custom_parameter"] = "custom_value"; - context.Response["parameter_with_multiple_values"] = new[] - { - "custom_value_1", - "custom_value_2" - }; - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Response["custom_parameter"] = "custom_value"; + context.Response["parameter_with_multiple_values"] = new[] + { + "custom_value_1", + "custom_value_2" + }; - // Act - var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/userinfo", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); - // Assert - Assert.Equal("custom_value", (string?) response["custom_parameter"]); - Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); - } + // Assert + Assert.Equal("custom_value", (string?) response["custom_parameter"]); + Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]?) response["parameter_with_multiple_values"]); } } diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs index 91e6f35f..56cd0305 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs @@ -23,3354 +23,3353 @@ using static OpenIddict.Server.OpenIddictServerHandlers; using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.IntegrationTests +namespace OpenIddict.Server.IntegrationTests; + +public abstract partial class OpenIddictServerIntegrationTests { - public abstract partial class OpenIddictServerIntegrationTests + protected OpenIddictServerIntegrationTests(ITestOutputHelper outputHelper) { - protected OpenIddictServerIntegrationTests(ITestOutputHelper outputHelper) - { - OutputHelper = outputHelper; - } + OutputHelper = outputHelper; + } - protected ITestOutputHelper OutputHelper { get; } + protected ITestOutputHelper OutputHelper { get; } - [Fact] - public async Task ProcessAuthentication_UnknownEndpointCausesAnException() - { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + [Fact] + public async Task ProcessAuthentication_UnknownEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.PostAsync("/authenticate", new OpenIddictRequest()); - }); + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/authenticate", new OpenIddictRequest()); + }); - Assert.Equal(SR.GetResourceString(SR.ID0002), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0002), exception.Message); + } - [Fact] - public async Task ProcessAuthentication_InvalidEndpointCausesAnException() + [Fact] + public async Task ProcessAuthentication_InvalidEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetConfigurationEndpointUris("/authenticate"); + options.EnableDegradedMode(); + options.SetConfigurationEndpointUris("/authenticate"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.GetAsync("/authenticate"); - }); + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/authenticate"); + }); - Assert.Equal(SR.GetResourceString(SR.ID0002), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0002), exception.Message); + } - [Fact] - public async Task ProcessAuthentication_UnsupportedGrantTypeThrowsAnException() + [Fact] + public async Task ProcessAuthentication_UnsupportedGrantTypeThrowsAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/authenticate"); + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/authenticate"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/authenticate", new OpenIddictRequest { - return client.PostAsync("/authenticate", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - }); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", }); + }); - Assert.Equal(SR.GetResourceString(SR.ID0001), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0001), exception.Message); + } - [Fact] - public async Task ProcessAuthentication_MissingAccessTokenReturnsNull() + [Fact] + public async Task ProcessAuthentication_MissingAccessTokenReturnsNull() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetLogoutEndpointUris("/authenticate"); + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/authenticate"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = null - }); + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest + { + AccessToken = null + }); - // Assert - Assert.Null((string?) response[Claims.Subject]); - } + // Assert + Assert.Null((string?) response[Claims.Subject]); + } - [Fact] - public async Task ProcessAuthentication_InvalidAccessTokenReturnsNull() + [Fact] + public async Task ProcessAuthentication_InvalidAccessTokenReturnsNull() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetLogoutEndpointUris("/authenticate"); + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/authenticate"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "38323A4B-6CB2-41B8-B457-1951987CB383" - }); + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest + { + AccessToken = "38323A4B-6CB2-41B8-B457-1951987CB383" + }); - // Assert - Assert.Null((string?) response[Claims.Subject]); - } + // Assert + Assert.Null((string?) response[Claims.Subject]); + } - [Fact] - public async Task ProcessAuthentication_ValidAccessTokenReturnsExpectedIdentity() + [Fact] + public async Task ProcessAuthentication_ValidAccessTokenReturnsExpectedIdentity() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate"); + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - }); - - await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - AccessToken = "access_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessAuthentication_MissingIdTokenHintReturnsNull() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetLogoutEndpointUris("/authenticate"); + AccessToken = "access_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + } - return default; - })); - }); + [Fact] + public async Task ProcessAuthentication_MissingIdTokenHintReturnsNull() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/authenticate"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - IdTokenHint = null - }); + return default; + })); + }); - // Assert - Assert.Null((string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessAuthentication_InvalidIdTokenHintReturnsNull() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetLogoutEndpointUris("/authenticate"); + IdTokenHint = null + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Null((string?) response[Claims.Subject]); + } - return default; - })); - }); + [Fact] + public async Task ProcessAuthentication_InvalidIdTokenHintReturnsNull() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/authenticate"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - IdTokenHint = "38323A4B-6CB2-41B8-B457-1951987CB383" - }); + return default; + })); + }); - // Assert - Assert.Null((string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessAuthentication_ValidIdTokenHintReturnsExpectedIdentity() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetLogoutEndpointUris("/authenticate"); + IdTokenHint = "38323A4B-6CB2-41B8-B457-1951987CB383" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Null((string?) response[Claims.Subject]); + } - return default; - })); + [Fact] + public async Task ProcessAuthentication_ValidIdTokenHintReturnsExpectedIdentity() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetLogoutEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("id_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.IdToken }, context.ValidTokenTypes); + context.SkipRequest(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.IdToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("id_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.IdToken }, context.ValidTokenTypes); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.IdToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.GetAsync("/authenticate", new OpenIddictRequest - { - IdTokenHint = "id_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessAuthentication_MissingAuthorizationCodeReturnsNull() + // Act + var response = await client.GetAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/authenticate"); + IdTokenHint = "id_token" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + } - return default; - })); - }); + [Fact] + public async Task ProcessAuthentication_MissingAuthorizationCodeReturnsNull() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/authenticate"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/authenticate", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = null, - GrantType = GrantTypes.AuthorizationCode - }); + return default; + })); + }); - // Assert - Assert.Null((string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessAuthentication_InvalidAuthorizationCodeReturnsNull() + // Act + var response = await client.PostAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/authenticate"); + ClientId = "Fabrikam", + Code = null, + GrantType = GrantTypes.AuthorizationCode + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Null((string?) response[Claims.Subject]); + } - return default; - })); - }); + [Fact] + public async Task ProcessAuthentication_InvalidAuthorizationCodeReturnsNull() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/authenticate"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/authenticate", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "38323A4B-6CB2-41B8-B457-1951987CB383", - GrantType = GrantTypes.AuthorizationCode - }); + return default; + })); + }); - // Assert - Assert.Null((string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessAuthentication_ValidAuthorizationCodeReturnsExpectedIdentity() + // Act + var response = await client.PostAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/authenticate"); + ClientId = "Fabrikam", + Code = "38323A4B-6CB2-41B8-B457-1951987CB383", + GrantType = GrantTypes.AuthorizationCode + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Null((string?) response[Claims.Subject]); + } - return default; - })); + [Fact] + public async Task ProcessAuthentication_ValidAuthorizationCodeReturnsExpectedIdentity() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("authorization_code", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + context.SkipRequest(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetPresenters("Fabrikam"); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("authorization_code", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetPresenters("Fabrikam"); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/authenticate", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "authorization_code", - GrantType = GrantTypes.AuthorizationCode + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessAuthentication_MissingRefreshTokenReturnsNull() + // Act + var response = await client.PostAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/authenticate"); + ClientId = "Fabrikam", + Code = "authorization_code", + GrantType = GrantTypes.AuthorizationCode + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + } - return default; - })); - }); + [Fact] + public async Task ProcessAuthentication_MissingRefreshTokenReturnsNull() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/authenticate"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/authenticate", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = null - }); + return default; + })); + }); - // Assert - Assert.Null((string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessAuthentication_InvalidRefreshTokenReturnsNull() + // Act + var response = await client.PostAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/authenticate"); + GrantType = GrantTypes.RefreshToken, + RefreshToken = null + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Null((string?) response[Claims.Subject]); + } - return default; - })); - }); + [Fact] + public async Task ProcessAuthentication_InvalidRefreshTokenReturnsNull() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/authenticate"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/authenticate", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "38323A4B-6CB2-41B8-B457-1951987CB383" - }); + return default; + })); + }); - // Assert - Assert.Null((string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessAuthentication_ValidRefreshTokenReturnsExpectedIdentity() + // Act + var response = await client.PostAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/authenticate"); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "38323A4B-6CB2-41B8-B457-1951987CB383" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Null((string?) response[Claims.Subject]); + } - return default; - })); + [Fact] + public async Task ProcessAuthentication_ValidRefreshTokenReturnsExpectedIdentity() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/authenticate"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("refresh_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + context.SkipRequest(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("refresh_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/authenticate", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "refresh_token" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessChallenge_UnknownEndpointCausesAnException() + // Act + var response = await client.PostAsync("/authenticate", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "refresh_token" + }); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.PostAsync("/challenge", new OpenIddictRequest()); - }); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response[Claims.Subject]); + } - Assert.Equal(SR.GetResourceString(SR.ID0006), exception.Message); - } + [Fact] + public async Task ProcessChallenge_UnknownEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessChallenge_InvalidEndpointCausesAnException() + // Act and assert + var exception = await Assert.ThrowsAsync(delegate { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetConfigurationEndpointUris("/challenge"); + return client.PostAsync("/challenge", new OpenIddictRequest()); + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + Assert.Equal(SR.GetResourceString(SR.ID0006), exception.Message); + } - return default; - })); - }); + [Fact] + public async Task ProcessChallenge_InvalidEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetConfigurationEndpointUris("/challenge"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.GetAsync("/challenge"); - }); + return default; + })); + }); - Assert.Equal(SR.GetResourceString(SR.ID0006), exception.Message); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessChallenge_ReturnsDefaultErrorForAuthorizationRequestsWhenNoneIsSpecified() + // Act and assert + var exception = await Assert.ThrowsAsync(delegate { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetAuthorizationEndpointUris("/challenge"); + return client.GetAsync("/challenge"); + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + Assert.Equal(SR.GetResourceString(SR.ID0006), exception.Message); + } - return default; - })); - }); + [Fact] + public async Task ProcessChallenge_ReturnsDefaultErrorForAuthorizationRequestsWhenNoneIsSpecified() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetAuthorizationEndpointUris("/challenge"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/challenge", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + return default; + })); + }); - // Assert - Assert.Equal(Errors.AccessDenied, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2015), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2015), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessChallenge_ReturnsDefaultErrorForTokenRequestsWhenNoneIsSpecified() + // Act + var response = await client.PostAsync("/challenge", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/challenge"); + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal(Errors.AccessDenied, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2015), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2015), response.ErrorUri); + } - return default; - })); - }); + [Fact] + public async Task ProcessChallenge_ReturnsDefaultErrorForTokenRequestsWhenNoneIsSpecified() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/challenge"); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/challenge", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + return default; + })); + }); - // Assert - Assert.Equal(Errors.InvalidGrant, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2024), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2024), response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessChallenge_ReturnsDefaultErrorForUserinfoRequestsWhenNoneIsSpecified() + // Act + var response = await client.PostAsync("/challenge", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/challenge"); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SlAV32hkKG", context.Token); + // Assert + Assert.Equal(Errors.InvalidGrant, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2024), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2024), response.ErrorUri); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + [Fact] + public async Task ProcessChallenge_ReturnsDefaultErrorForUserinfoRequestsWhenNoneIsSpecified() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/challenge"); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("SlAV32hkKG", context.Token); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + return default; + }); - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - // Act - var response = await client.PostAsync("/challenge", new OpenIddictRequest - { - AccessToken = "SlAV32hkKG" - }); + return default; + })); + }); - // Assert - Assert.Equal(Errors.InsufficientAccess, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2025), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2025), response.ErrorUri); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ProcessChallenge_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/challenge"); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Act + var response = await client.PostAsync("/challenge", new OpenIddictRequest + { + AccessToken = "SlAV32hkKG" + }); - return default; - })); + // Assert + Assert.Equal(Errors.InsufficientAccess, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2025), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2025), response.ErrorUri); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ProcessChallenge_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/challenge"); - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - await using var client = await server.CreateClientAsync(); + return default; + })); - // Act - var response = await client.PostAsync("/challenge", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + return default; + })); + }); - [Fact] - public async Task ProcessChallenge_AllowsHandlingResponse() + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/challenge", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/challenge"); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - return default; - })); + [Fact] + public async Task ProcessChallenge_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/challenge"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - context.HandleRequest(); + return default; + })); - return default; - })); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new + { + name = "Bob le Bricoleur" + }); - await using var client = await server.CreateClientAsync(); + context.HandleRequest(); - // Act - var response = await client.PostAsync("/challenge", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + return default; + })); + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_UnknownEndpointCausesAnException() + // Act + var response = await client.PostAsync("/challenge", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.PostAsync("/signin", new OpenIddictRequest()); - }); + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - Assert.Equal(SR.GetResourceString(SR.ID0010), exception.Message); - } + [Fact] + public async Task ProcessSignIn_UnknownEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_InvalidEndpointCausesAnException() + // Act and assert + var exception = await Assert.ThrowsAsync(delegate { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetConfigurationEndpointUris("/signin"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); - - return default; - })); - }); - - await using var client = await server.CreateClientAsync(); - - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.GetAsync("/signin"); - }); + return client.PostAsync("/signin", new OpenIddictRequest()); + }); - Assert.Equal(SR.GetResourceString(SR.ID0010), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0010), exception.Message); + } - [Fact] - public async Task ProcessSignIn_NullIdentityCausesAnException() + [Fact] + public async Task ProcessSignIn_InvalidEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); + options.SetConfigurationEndpointUris("/signin"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); - }); + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/signin"); + }); - Assert.Equal(SR.GetResourceString(SR.ID0011), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0010), exception.Message); + } - [Fact] - public async Task ProcessSignIn_NullAuthenticationTypeCausesAnException() + [Fact] + public async Task ProcessSignIn_NullIdentityCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/connect/token", new OpenIddictRequest { - return client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" }); + }); - Assert.Equal(SR.GetResourceString(SR.ID0014), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0011), exception.Message); + } - [Fact] - public async Task ProcessSignIn_AuthenticatedIdentityFromDeviceEndpointCausesAnException() + [Fact] + public async Task ProcessSignIn_NullAuthenticationTypeCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Test")); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/connect/token", new OpenIddictRequest { - return client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" }); + }); - Assert.Equal(SR.GetResourceString(SR.ID0012), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0014), exception.Message); + } - [Fact] - public async Task ProcessSignIn_MissingSubjectCausesAnException() + [Fact] + public async Task ProcessSignIn_AuthenticatedIdentityFromDeviceEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Test")); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/connect/device", new OpenIddictRequest { - return client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code - }); + ClientId = "Fabrikam" }); + }); - Assert.Equal(SR.GetResourceString(SR.ID0015), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0012), exception.Message); + } - [Fact] - public async Task ProcessSignIn_NonNullSubjectFromDeviceEndpointCausesAnException() + [Fact] + public async Task ProcessSignIn_MissingSubjectCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/connect/authorize", new OpenIddictRequest { - return client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code }); + }); - Assert.Equal(SR.GetResourceString(SR.ID0013), exception.Message); - } + Assert.Equal(SR.GetResourceString(SR.ID0015), exception.Message); + } - [Fact] - public async Task ProcessSignIn_ScopeDefaultsToOpenId() + [Fact] + public async Task ProcessSignIn_NonNullSubjectFromDeviceEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - Assert.Equal(new[] { Scopes.OpenId }, context.Principal!.GetScopes()); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/connect/device", new OpenIddictRequest { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - Scope = Scopes.OpenId + ClientId = "Fabrikam" }); + }); - // Assert - Assert.NotNull(response.AccessToken); - } + Assert.Equal(SR.GetResourceString(SR.ID0013), exception.Message); + } - [Fact] - public async Task ProcessSignIn_ResourcesAreInferredFromAudiences() + [Fact] + public async Task ProcessSignIn_ScopeDefaultsToOpenId() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetAudiences("http://www.fabrikam.com/") - .SetScopes(Scopes.OfflineAccess) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - Assert.Equal(new[] { "http://www.fabrikam.com/" }, context.Principal!.GetResources()); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + Assert.Equal(new[] { Scopes.OpenId }, context.Principal!.GetScopes()); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = Scopes.OpenId + }); - // Assert - Assert.NotNull(response.AccessToken); - Assert.NotNull(response.RefreshToken); - } + // Assert + Assert.NotNull(response.AccessToken); + } - [Fact] - public async Task ProcessSignIn_AllowsOverridingDefaultTokensSelection() + [Fact] + public async Task ProcessSignIn_ResourcesAreInferredFromAudiences() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetAudiences("http://www.fabrikam.com/") + .SetScopes(Scopes.OfflineAccess) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - context.GenerateAccessToken = context.IncludeAccessToken = false; - context.GenerateAuthorizationCode = context.IncludeAuthorizationCode = true; - context.GenerateDeviceCode = context.IncludeDeviceCode = true; - context.GenerateIdentityToken = context.IncludeIdentityToken = true; - context.GenerateRefreshToken = context.IncludeRefreshToken = true; - context.GenerateUserCode = context.IncludeUserCode = true; - - return default; - }); + Assert.Equal(new[] { "http://www.fabrikam.com/" }, context.Principal!.GetResources()); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); - }); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - // Assert - Assert.Null(response.AccessToken); - Assert.NotNull(response.Code); - Assert.NotNull(response.DeviceCode); - Assert.NotNull(response.IdToken); - Assert.NotNull(response.RefreshToken); - Assert.NotNull(response.UserCode); - } + // Assert + Assert.NotNull(response.AccessToken); + Assert.NotNull(response.RefreshToken); + } - [Fact] - public async Task ProcessSignIn_NoTokenIsReturnedForNoneFlowRequests() + [Fact] + public async Task ProcessSignIn_AllowsOverridingDefaultTokensSelection() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.False(context.GenerateAccessToken); - Assert.False(context.GenerateAuthorizationCode); - Assert.False(context.GenerateDeviceCode); - Assert.False(context.GenerateIdentityToken); - Assert.False(context.GenerateRefreshToken); - Assert.False(context.GenerateUserCode); - Assert.False(context.IncludeAccessToken); - Assert.False(context.IncludeAuthorizationCode); - Assert.False(context.IncludeDeviceCode); - Assert.False(context.IncludeIdentityToken); - Assert.False(context.IncludeRefreshToken); - Assert.False(context.IncludeUserCode); - - return default; - }); - - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + context.GenerateAccessToken = context.IncludeAccessToken = false; + context.GenerateAuthorizationCode = context.IncludeAuthorizationCode = true; + context.GenerateDeviceCode = context.IncludeDeviceCode = true; + context.GenerateIdentityToken = context.IncludeIdentityToken = true; + context.GenerateRefreshToken = context.IncludeRefreshToken = true; + context.GenerateUserCode = context.IncludeUserCode = true; + + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.None, - Scope = Scopes.OpenId + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.Equal(0, response.Count); - } + await using var client = await server.CreateClientAsync(); - [Theory] - [InlineData("code id_token token")] - [InlineData("code token")] - [InlineData("id_token token")] - [InlineData("token")] - public async Task ProcessSignIn_AnAccessTokenIsReturnedForImplicitAndHybridFlowRequests(string type) + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.Null(response.AccessToken); + Assert.NotNull(response.Code); + Assert.NotNull(response.DeviceCode); + Assert.NotNull(response.IdToken); + Assert.NotNull(response.RefreshToken); + Assert.NotNull(response.UserCode); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_NoTokenIsReturnedForNoneFlowRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateAccessToken); - Assert.True(context.IncludeAccessToken); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - }); + return default; + })); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.False(context.GenerateAccessToken); + Assert.False(context.GenerateAuthorizationCode); + Assert.False(context.GenerateDeviceCode); + Assert.False(context.GenerateIdentityToken); + Assert.False(context.GenerateRefreshToken); + Assert.False(context.GenerateUserCode); + Assert.False(context.IncludeAccessToken); + Assert.False(context.IncludeAuthorizationCode); + Assert.False(context.IncludeDeviceCode); + Assert.False(context.IncludeIdentityToken); + Assert.False(context.IncludeRefreshToken); + Assert.False(context.IncludeUserCode); + + return default; }); - }); - - await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnAccessTokenIsReturnedForCodeGrantRequests() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.None, + Scope = Scopes.OpenId + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + // Assert + Assert.Equal(0, response.Count); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + [Theory] + [InlineData("code id_token token")] + [InlineData("code token")] + [InlineData("id_token token")] + [InlineData("token")] + public async Task ProcessSignIn_AnAccessTokenIsReturnedForImplicitAndHybridFlowRequests(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateAccessToken); - Assert.True(context.IncludeAccessToken); - - return default; - }); + Assert.True(context.GenerateAccessToken); + Assert.True(context.IncludeAccessToken); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - }); - - await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnAccessTokenIsReturnedForDeviceGrantRequests() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token); - Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) - .SetTokenType(TokenTypeHints.DeviceCode) - .SetPresenters("Fabrikam"); + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); - return default; - }); + // Assert + Assert.NotNull(response.AccessToken); + } - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + [Fact] + public async Task ProcessSignIn_AnAccessTokenIsReturnedForCodeGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateAccessToken); - Assert.True(context.IncludeAccessToken); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateAccessToken); + Assert.True(context.IncludeAccessToken); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - DeviceCode = "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", - GrantType = GrantTypes.DeviceCode - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode + }); - // Assert - Assert.NotNull(response.AccessToken); - } + // Assert + Assert.NotNull(response.AccessToken); + } - [Fact] - public async Task ProcessSignIn_AnAccessTokenIsReturnedForRefreshTokenGrantRequests() + [Fact] + public async Task ProcessSignIn_AnAccessTokenIsReturnedForDeviceGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token); + Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) + .SetTokenType(TokenTypeHints.DeviceCode) + .SetPresenters("Fabrikam"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateAccessToken); - Assert.True(context.IncludeAccessToken); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateAccessToken); + Assert.True(context.IncludeAccessToken); + + return default; + }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnAccessTokenIsReturnedForPasswordGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + DeviceCode = "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", + GrantType = GrantTypes.DeviceCode + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.NotNull(response.AccessToken); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_AnAccessTokenIsReturnedForRefreshTokenGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateAccessToken); - Assert.True(context.IncludeAccessToken); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateAccessToken); + Assert.True(context.IncludeAccessToken); + + return default; + }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnAccessTokenIsReturnedForClientCredentialsGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.NotNull(response.AccessToken); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_AnAccessTokenIsReturnedForPasswordGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateAccessToken); - Assert.True(context.IncludeAccessToken); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - }); + return default; + })); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); - }); - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateAccessToken); + Assert.True(context.IncludeAccessToken); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - GrantType = GrantTypes.ClientCredentials, + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnAccessTokenIsReturnedForCustomGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.NotNull(response.AccessToken); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_AnAccessTokenIsReturnedForClientCredentialsGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateAccessToken); - Assert.True(context.IncludeAccessToken); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - }); + return default; + })); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); - }); - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateAccessToken); + Assert.True(context.IncludeAccessToken); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = "urn:ietf:params:oauth:grant-type:custom_grant" + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Theory] - [InlineData("code")] - [InlineData("code id_token")] - [InlineData("code id_token token")] - [InlineData("code token")] - public async Task ProcessSignIn_AnAuthorizationCodeIsReturnedForCodeAndHybridFlowRequests(string type) + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + GrantType = GrantTypes.ClientCredentials, + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.NotNull(response.AccessToken); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_AnAccessTokenIsReturnedForCustomGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateAuthorizationCode); - Assert.True(context.IncludeAuthorizationCode); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - }); + return default; + })); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); - }); - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateAccessToken); + Assert.True(context.IncludeAccessToken); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.Code); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_ADeviceCodeIsReturnedForDeviceRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + GrantType = "urn:ietf:params:oauth:grant-type:custom_grant" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); + // Assert + Assert.NotNull(response.AccessToken); + } - return default; - })); + [Theory] + [InlineData("code")] + [InlineData("code id_token")] + [InlineData("code id_token token")] + [InlineData("code token")] + public async Task ProcessSignIn_AnAuthorizationCodeIsReturnedForCodeAndHybridFlowRequests(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateDeviceCode); - Assert.True(context.IncludeDeviceCode); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - }); + return default; + })); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); - }); - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateAuthorizationCode); + Assert.True(context.IncludeAuthorizationCode); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.DeviceCode); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_ScopesCanBeOverridenForRefreshTokenRequests() + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RegisterScopes(Scopes.Profile); + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + // Assert + Assert.NotNull(response.Code); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetScopes(Scopes.Profile, Scopes.OfflineAccess) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + [Fact] + public async Task ProcessSignIn_ADeviceCodeIsReturnedForDeviceRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal(new[] { Scopes.Profile }, context.AccessTokenPrincipal?.GetScopes()); - - return default; - }); + Assert.True(context.GenerateDeviceCode); + Assert.True(context.IncludeDeviceCode); - builder.SetOrder(PrepareAccessTokenPrincipal.Descriptor.Order + 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8", - Scope = Scopes.Profile + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.AccessToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_NoRefreshTokenIsReturnedWhenOfflineAccessScopeIsNotGranted() + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.NotNull(response.DeviceCode); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_ScopesCanBeOverridenForRefreshTokenRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.RegisterScopes(Scopes.Profile); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.False(context.GenerateRefreshToken); - Assert.False(context.IncludeRefreshToken); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetScopes(Scopes.Profile, Scopes.OfflineAccess) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" + builder.UseInlineHandler(context => + { + Assert.Equal(new[] { Scopes.Profile }, context.AccessTokenPrincipal?.GetScopes()); + + return default; + }); + + builder.SetOrder(PrepareAccessTokenPrincipal.Descriptor.Order + 500); }); + }); - // Assert - Assert.Null(response.RefreshToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_ARefreshTokenIsReturnedForCodeGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8", + Scope = Scopes.Profile + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + // Assert + Assert.NotNull(response.AccessToken); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetScopes(Scopes.OfflineAccess) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + [Fact] + public async Task ProcessSignIn_NoRefreshTokenIsReturnedWhenOfflineAccessScopeIsNotGranted() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateRefreshToken); - Assert.True(context.IncludeRefreshToken); - - return default; - }); + Assert.False(context.GenerateRefreshToken); + Assert.False(context.IncludeRefreshToken); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - }); - - await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.RefreshToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_ARefreshTokenIsReturnedForDeviceGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token); - Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) - .SetTokenType(TokenTypeHints.DeviceCode) - .SetPresenters("Fabrikam"); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - return default; - }); + // Assert + Assert.Null(response.RefreshToken); + } - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + [Fact] + public async Task ProcessSignIn_ARefreshTokenIsReturnedForCodeGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetScopes(Scopes.OfflineAccess) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetScopes(Scopes.OfflineAccess) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateRefreshToken); - Assert.True(context.IncludeRefreshToken); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateRefreshToken); + Assert.True(context.IncludeRefreshToken); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - DeviceCode = "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", - GrantType = GrantTypes.DeviceCode - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode + }); - // Assert - Assert.NotNull(response.RefreshToken); - } + // Assert + Assert.NotNull(response.RefreshToken); + } - [Fact] - public async Task ProcessSignIn_ARefreshTokenIsReturnedForRefreshTokenGrantRequests() + [Fact] + public async Task ProcessSignIn_ARefreshTokenIsReturnedForDeviceGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetScopes(Scopes.OfflineAccess) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token); + Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) + .SetTokenType(TokenTypeHints.DeviceCode) + .SetPresenters("Fabrikam"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateRefreshToken); - Assert.True(context.IncludeRefreshToken); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetScopes(Scopes.OfflineAccess) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - // Assert - Assert.NotNull(response.RefreshToken); - } - - [Fact] - public async Task ProcessSignIn_ARefreshTokenIsReturnedForPasswordGrantRequests() - { - // Arrange - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateRefreshToken); - Assert.True(context.IncludeRefreshToken); - - return default; - }); + Assert.True(context.GenerateRefreshToken); + Assert.True(context.IncludeRefreshToken); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetScopes(Scopes.OfflineAccess) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + DeviceCode = "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", + GrantType = GrantTypes.DeviceCode + }); - // Assert - Assert.NotNull(response.RefreshToken); - } + // Assert + Assert.NotNull(response.RefreshToken); + } - [Fact] - public async Task ProcessSignIn_ARefreshTokenIsReturnedForClientCredentialsGrantRequests() + [Fact] + public async Task ProcessSignIn_ARefreshTokenIsReturnedForRefreshTokenGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateRefreshToken); - Assert.True(context.IncludeRefreshToken); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetScopes(Scopes.OfflineAccess) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetScopes(Scopes.OfflineAccess) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - GrantType = GrantTypes.ClientCredentials, + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateRefreshToken); + Assert.True(context.IncludeRefreshToken); + + return default; + }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.RefreshToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_ARefreshTokenIsReturnedForCustomGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateRefreshToken); - Assert.True(context.IncludeRefreshToken); + // Assert + Assert.NotNull(response.RefreshToken); + } - return default; - }); + [Fact] + public async Task ProcessSignIn_ARefreshTokenIsReturnedForPasswordGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateRefreshToken); + Assert.True(context.IncludeRefreshToken); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetScopes(Scopes.OfflineAccess) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + return default; + }); - return default; - })); + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetScopes(Scopes.OfflineAccess) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = "urn:ietf:params:oauth:grant-type:custom_grant" - }); + return default; + })); + }); - // Assert - Assert.NotNull(response.RefreshToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_NoIdentityTokenIsReturnedWhenOfflineAccessScopeIsNotGranted() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.NotNull(response.RefreshToken); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_ARefreshTokenIsReturnedForClientCredentialsGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.False(context.GenerateIdentityToken); - Assert.False(context.IncludeIdentityToken); - - return default; - }); + Assert.True(context.GenerateRefreshToken); + Assert.True(context.IncludeRefreshToken); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetScopes(Scopes.OfflineAccess) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + return default; + })); + }); - // Assert - Assert.Null(response.IdToken); - } + await using var client = await server.CreateClientAsync(); - [Theory] - [InlineData("code id_token")] - [InlineData("code id_token token")] - [InlineData("id_token")] - [InlineData("id_token token")] - public async Task ProcessSignIn_AnIdentityTokenIsReturnedForImplicitAndHybridFlowRequests(string type) + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + GrantType = GrantTypes.ClientCredentials, + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.NotNull(response.RefreshToken); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_ARefreshTokenIsReturnedForCustomGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateIdentityToken); - Assert.True(context.IncludeIdentityToken); - - return default; - }); + Assert.True(context.GenerateRefreshToken); + Assert.True(context.IncludeRefreshToken); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetScopes(Scopes.OfflineAccess) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - Nonce = "n-0S6_WzA2Mj", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = type, - Scope = Scopes.OpenId - }); + return default; + })); + }); - // Assert - Assert.NotNull(response.IdToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnIdentityTokenIsReturnedForCodeGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + GrantType = "urn:ietf:params:oauth:grant-type:custom_grant" + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + // Assert + Assert.NotNull(response.RefreshToken); + } - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetScopes(Scopes.OpenId) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + [Fact] + public async Task ProcessSignIn_NoIdentityTokenIsReturnedWhenOfflineAccessScopeIsNotGranted() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - return default; - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateIdentityToken); - Assert.True(context.IncludeIdentityToken); + Assert.False(context.GenerateIdentityToken); + Assert.False(context.IncludeIdentityToken); - return default; - }); - - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - // Assert - Assert.NotNull(response.IdToken); - } + // Assert + Assert.Null(response.IdToken); + } - [Fact] - public async Task ProcessSignIn_AnIdentityTokenIsReturnedForDeviceGrantRequests() + [Theory] + [InlineData("code id_token")] + [InlineData("code id_token token")] + [InlineData("id_token")] + [InlineData("id_token token")] + public async Task ProcessSignIn_AnIdentityTokenIsReturnedForImplicitAndHybridFlowRequests(string type) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token); - Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) - .SetTokenType(TokenTypeHints.DeviceCode) - .SetPresenters("Fabrikam"); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateIdentityToken); + Assert.True(context.IncludeIdentityToken); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetScopes(Scopes.OpenId) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + }); + }); - return default; - }); + await using var client = await server.CreateClientAsync(); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + Nonce = "n-0S6_WzA2Mj", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = type, + Scope = Scopes.OpenId + }); + + // Assert + Assert.NotNull(response.IdToken); + } - options.AddEventHandler(builder => + [Fact] + public async Task ProcessSignIn_AnIdentityTokenIsReturnedForCodeGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateIdentityToken); - Assert.True(context.IncludeIdentityToken); + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetScopes(Scopes.OpenId) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - ClientId = "Fabrikam", - DeviceCode = "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", - GrantType = GrantTypes.DeviceCode + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateIdentityToken); + Assert.True(context.IncludeIdentityToken); + + return default; + }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.IdToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnIdentityTokenIsReturnedForRefreshTokenGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode + }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + // Assert + Assert.NotNull(response.IdToken); + } + + [Fact] + public async Task ProcessSignIn_AnIdentityTokenIsReturnedForDeviceGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetScopes(Scopes.OpenId) - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token); + Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()) + .SetTokenType(TokenTypeHints.DeviceCode) + .SetPresenters("Fabrikam"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateIdentityToken); - Assert.True(context.IncludeIdentityToken); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetScopes(Scopes.OpenId) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - }); - await using var client = await server.CreateClientAsync(); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateIdentityToken); + Assert.True(context.IncludeIdentityToken); + + return default; + }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.IdToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnIdentityTokenIsReturnedForPasswordGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + DeviceCode = "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", + GrantType = GrantTypes.DeviceCode + }); + + // Assert + Assert.NotNull(response.IdToken); + } + + [Fact] + public async Task ProcessSignIn_AnIdentityTokenIsReturnedForRefreshTokenGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateIdentityToken); - Assert.True(context.IncludeIdentityToken); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetScopes(Scopes.OpenId) + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.AddEventHandler(builder => { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - Scope = Scopes.OpenId + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateIdentityToken); + Assert.True(context.IncludeIdentityToken); + + return default; + }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.IdToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnIdentityTokenIsReturnedForClientCredentialsGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.NotNull(response.IdToken); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_AnIdentityTokenIsReturnedForPasswordGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateIdentityToken); - Assert.True(context.IncludeIdentityToken); - - return default; - }); + Assert.True(context.GenerateIdentityToken); + Assert.True(context.IncludeIdentityToken); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + return default; }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - GrantType = GrantTypes.ClientCredentials, - Scope = Scopes.OpenId - }); + return default; + })); + }); - // Assert - Assert.NotNull(response.IdToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AnIdentityTokenIsReturnedForCustomGrantRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = Scopes.OpenId + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Assert + Assert.NotNull(response.IdToken); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_AnIdentityTokenIsReturnedForClientCredentialsGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateIdentityToken); - Assert.True(context.IncludeIdentityToken); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - }); + return default; + })); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); - }); - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateIdentityToken); + Assert.True(context.IncludeIdentityToken); - await using var client = await server.CreateClientAsync(); + return default; + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = "urn:ietf:params:oauth:grant-type:custom_grant", - Scope = Scopes.OpenId + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - // Assert - Assert.NotNull(response.IdToken); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AUserCodeIsReturnedForDeviceRequests() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + GrantType = GrantTypes.ClientCredentials, + Scope = Scopes.OpenId + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); + // Assert + Assert.NotNull(response.IdToken); + } - return default; - })); + [Fact] + public async Task ProcessSignIn_AnIdentityTokenIsReturnedForCustomGrantRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.True(context.GenerateUserCode); - Assert.True(context.IncludeUserCode); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - }); + return default; + })); - builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateIdentityToken); + Assert.True(context.IncludeIdentityToken); + + return default; }); + + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/device", new OpenIddictRequest - { - ClientId = "Fabrikam" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = "urn:ietf:params:oauth:grant-type:custom_grant", + Scope = Scopes.OpenId + }); - // Assert - Assert.NotNull(response.UserCode); - } + // Assert + Assert.NotNull(response.IdToken); + } - [Fact] - public async Task ProcessSignIn_PrivateClaimsAreAutomaticallyRestored() + [Fact] + public async Task ProcessSignIn_AUserCodeIsReturnedForDeviceRequests() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity()); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) - .SetClaim(Claims.Subject, "Bob le Bricoleur") - .SetClaim(Claims.Prefixes.Private + "_private_claim", "value"); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + Assert.True(context.GenerateUserCode); + Assert.True(context.IncludeUserCode); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - Assert.Equal(new[] { Scopes.OpenId, Scopes.OfflineAccess }, context.Principal!.GetScopes()); - Assert.Equal("value", context.Principal!.GetClaim(Claims.Prefixes.Private + "_private_claim")); - - return default; - })); + builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + // Act + var response = await client.PostAsync("/connect/device", new OpenIddictRequest + { + ClientId = "Fabrikam" + }); - // Assert - Assert.NotNull(response.IdToken); - Assert.NotNull(response.RefreshToken); - } + // Assert + Assert.NotNull(response.UserCode); + } - [Fact] - public async Task ProcessSignIn_AuthorizationCodeIsAutomaticallyRedeemed() + [Fact] + public async Task ProcessSignIn_PrivateClaimsAreAutomaticallyRestored() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - var token = new OpenIddictToken(); + options.EnableDegradedMode(); - var manager = CreateTokenManager(mock => + options.AddEventHandler(builder => { - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); - - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + builder.UseInlineHandler(context => + { + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) + .SetClaim(Claims.Subject, "Bob le Bricoleur") + .SetClaim(Claims.Prefixes.Private + "_private_claim", "value"); - mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) - .ReturnsAsync(true); + return default; + }); - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OpenIddictToken()); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var server = await CreateServerAsync(options => - { - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); - Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); + Assert.Equal(new[] { Scopes.OpenId, Scopes.OfflineAccess }, context.Principal!.GetScopes()); + Assert.Equal("value", context.Principal!.GetClaim(Claims.Prefixes.Private + "_private_claim")); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AuthorizationCode) - .SetPresenters("Fabrikam") - .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + return default; + })); + }); - return default; - }); + await using var client = await server.CreateClientAsync(); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - options.Services.AddSingleton(CreateApplicationManager(mock => - { - var application = new OpenIddictApplication(); + // Assert + Assert.NotNull(response.IdToken); + Assert.NotNull(response.RefreshToken); + } - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + [Fact] + public async Task ProcessSignIn_AuthorizationCodeIsAutomaticallyRedeemed() + { + // Arrange + var token = new OpenIddictToken(); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); - })); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); - options.Services.AddSingleton(manager); - }); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - Code = "SplxlOBeZQQYbYS6WxSbIA", - GrantType = GrantTypes.AuthorizationCode, - RedirectUri = "http://www.fabrikam.com/path" - }); + mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) + .ReturnsAsync(true); - // Assert - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); + }); - [Fact] - public async Task ProcessSignIn_RefreshTokenIsAutomaticallyRedeemedWhenRollingTokensAreEnabled() + await using var server = await CreateServerAsync(options => { - // Arrange - var token = new OpenIddictToken(); - - var manager = CreateTokenManager(mock => + options.AddEventHandler(builder => { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); - - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); + builder.UseInlineHandler(context => + { + Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token); + Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AuthorizationCode) + .SetPresenters("Fabrikam") + .SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) - .ReturnsAsync(true); + return default; + }); - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OpenIddictToken()); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.DisableAuthorizationStorage(); + var application = new OpenIddictApplication(); - options.AddEventHandler(builder => - { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); + })); - return default; - }); + options.Services.AddSingleton(manager); + }); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); - }); + await using var client = await server.CreateClientAsync(); - options.Services.AddSingleton(manager); - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + Code = "SplxlOBeZQQYbYS6WxSbIA", + GrantType = GrantTypes.AuthorizationCode, + RedirectUri = "http://www.fabrikam.com/path" + }); - await using var client = await server.CreateClientAsync(); + // Assert + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Once()); + } - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + [Fact] + public async Task ProcessSignIn_RefreshTokenIsAutomaticallyRedeemedWhenRollingTokensAreEnabled() + { + // Arrange + var token = new OpenIddictToken(); - // Assert - Assert.NotNull(response.RefreshToken); + var manager = CreateTokenManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(token); - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); - [Fact] - public async Task ProcessSignIn_RefreshTokenIsNotRedeemedWhenRollingTokensAreDisabled() - { - // Arrange - var token = new OpenIddictToken(); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(false); - var manager = CreateTokenManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) - .ReturnsAsync(token); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) - .ReturnsAsync(false); + mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) + .ReturnsAsync(true); - mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); + }); - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OpenIddictToken()); - }); + await using var server = await CreateServerAsync(options => + { + options.DisableAuthorizationStorage(); - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.DisableAuthorizationStorage(); - options.DisableRollingRefreshTokens(); - - options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("8xLOxBtZp8", context.Token); - Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.RefreshToken) - .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) - .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") - .SetClaim(Claims.Subject, "Bob le Bricoleur"); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); - options.Services.AddSingleton(manager); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); - await using var client = await server.CreateClientAsync(); + options.Services.AddSingleton(manager); + }); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.RefreshToken, - RefreshToken = "8xLOxBtZp8" - }); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); + + // Assert + Assert.NotNull(response.RefreshToken); - // Assert - Assert.NotNull(response.RefreshToken); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Once()); + } - Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); - Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Never()); - } + [Fact] + public async Task ProcessSignIn_RefreshTokenIsNotRedeemedWhenRollingTokensAreDisabled() + { + // Arrange + var token = new OpenIddictToken(); - [Fact] - public async Task ProcessSignIn_AdHocAuthorizationIsAutomaticallyCreated() + var manager = CreateTokenManager(mock => { - // Arrange - var token = new OpenIddictToken(); + mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny())) + .ReturnsAsync(token); - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny())) - .ReturnsAsync(new OpenIddictAuthorization()); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) + .ReturnsAsync(false); - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OpenIddictAuthorization()); - }); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) + .ReturnsAsync(true); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); + }); + + await using var server = await CreateServerAsync(options => + { + options.DisableAuthorizationStorage(); + options.DisableRollingRefreshTokens(); - await using var server = await CreateServerAsync(options => + options.AddEventHandler(builder => { - options.Services.AddSingleton(CreateApplicationManager(mock => + builder.UseInlineHandler(context => { - var application = new OpenIddictApplication(); + Assert.Equal("8xLOxBtZp8", context.Token); + Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes); - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.RefreshToken) + .SetScopes(Scopes.OpenId, Scopes.OfflineAccess) + .SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103") + .SetClaim(Claims.Subject, "Bob le Bricoleur"); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + return default; + }); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + }); - mock.Setup(manager => manager.GetIdAsync(application, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - })); + options.Services.AddSingleton(manager); + }); - options.Services.AddSingleton(CreateTokenManager(mock => - { - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(token); + await using var client = await server.CreateClientAsync(); - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.RefreshToken, + RefreshToken = "8xLOxBtZp8" + }); - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); - })); + // Assert + Assert.NotNull(response.RefreshToken); - options.Services.AddSingleton(manager); + Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce()); + Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Never()); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + [Fact] + public async Task ProcessSignIn_AdHocAuthorizationIsAutomaticallyCreated() + { + // Arrange + var token = new OpenIddictToken(); - return default; - })); - }); + var manager = CreateAuthorizationManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny())) + .ReturnsAsync(new OpenIddictAuthorization()); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictAuthorization()); + }); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + await using var server = await CreateServerAsync(options => + { + options.Services.AddSingleton(CreateApplicationManager(mock => { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - }); + var application = new OpenIddictApplication(); - // Assert - Assert.NotNull(response.Code); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - Mock.Get(manager).Verify(manager => manager.CreateAsync( - It.Is(descriptor => - descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" && - descriptor.CreationDate != null && - descriptor.Subject == "Bob le Magnifique" && - descriptor.Type == AuthorizationTypes.AdHoc), - It.IsAny()), Times.Once()); - } + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); - [Fact] - public async Task ProcessSignIn_AdHocAuthorizationIsNotCreatedWhenAuthorizationStorageIsDisabled() - { - // Arrange - var token = new OpenIddictToken(); + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - var manager = CreateAuthorizationManager(mock => - { - mock.Setup(manager => manager.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny())) - .ReturnsAsync(new OpenIddictAuthorization()); - }); + mock.Setup(manager => manager.GetIdAsync(application, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + })); - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateTokenManager(mock => { - options.Services.AddSingleton(CreateApplicationManager(mock => - { - var application = new OpenIddictApplication(); - - mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) - .ReturnsAsync(application); + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(token); - mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) - .ReturnsAsync(true); + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); + })); - mock.Setup(manager => manager.GetIdAsync(application, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - })); + options.Services.AddSingleton(manager); - options.Services.AddSingleton(CreateTokenManager(mock => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(token); - - mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) - .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) - .ReturnsAsync(token); + return default; })); + }); - options.Services.AddSingleton(manager); + await using var client = await server.CreateClientAsync(); - options.DisableAuthorizationStorage(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); - - return default; - })); - }); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + }); - await using var client = await server.CreateClientAsync(); + // Assert + Assert.NotNull(response.Code); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - }); + Mock.Get(manager).Verify(manager => manager.CreateAsync( + It.Is(descriptor => + descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" && + descriptor.CreationDate != null && + descriptor.Subject == "Bob le Magnifique" && + descriptor.Type == AuthorizationTypes.AdHoc), + It.IsAny()), Times.Once()); + } - // Assert - Assert.NotNull(response.Code); + [Fact] + public async Task ProcessSignIn_AdHocAuthorizationIsNotCreatedWhenAuthorizationStorageIsDisabled() + { + // Arrange + var token = new OpenIddictToken(); - Mock.Get(manager).Verify(manager => manager.CreateAsync(It.IsAny(), It.IsAny()), Times.Never()); - } + var manager = CreateAuthorizationManager(mock => + { + mock.Setup(manager => manager.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny())) + .ReturnsAsync(new OpenIddictAuthorization()); + }); - [Fact] - public async Task ProcessSignIn_ExpiresInIsReturnedWhenExpirationDateIsKnown() + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => + options.Services.AddSingleton(CreateApplicationManager(mock => { - options.EnableDegradedMode(); + var application = new OpenIddictApplication(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) + .ReturnsAsync(application); - return default; - })); - }); + mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny())) + .ReturnsAsync(true); + + mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny())) + .ReturnsAsync(true); - await using var client = await server.CreateClientAsync(); + mock.Setup(manager => manager.GetIdAsync(application, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + })); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest + options.Services.AddSingleton(CreateTokenManager(mock => { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(token); - // Assert - Assert.NotNull(response.ExpiresIn); - } + mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) + .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); - [Fact] - public async Task ProcessSignIn_ScopesAreReturnedWhenTheyDifferFromRequestedScopes() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.RegisterScopes(Scopes.Phone, Scopes.Profile); + mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) + .ReturnsAsync(token); + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetScopes(Scopes.Profile) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + options.Services.AddSingleton(manager); - return default; - })); - }); + options.DisableAuthorizationStorage(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w", - Scope = "openid phone profile" - }); + return default; + })); + }); - // Assert - Assert.Equal(Scopes.Profile, response.Scope); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ProcessSignIn_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + }); - return default; - })); + // Assert + Assert.NotNull(response.Code); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Reject(error, description, uri); + Mock.Get(manager).Verify(manager => manager.CreateAsync(It.IsAny(), It.IsAny()), Times.Never()); + } - return default; - })); - }); + [Fact] + public async Task ProcessSignIn_ExpiresInIsReturnedWhenExpirationDateIsKnown() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - await using var client = await server.CreateClientAsync(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - ClientId = "Fabrikam", - RedirectUri = "http://www.fabrikam.com/path", - ResponseType = ResponseTypes.Code, - Scope = Scopes.OpenId - }); + return default; + })); + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignIn_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetClaim(Claims.Subject, "Bob le Magnifique"); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - return default; - })); + // Assert + Assert.NotNull(response.ExpiresIn); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + [Fact] + public async Task ProcessSignIn_ScopesAreReturnedWhenTheyDifferFromRequestedScopes() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.RegisterScopes(Scopes.Phone, Scopes.Profile); - context.HandleRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetScopes(Scopes.Profile) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + Scope = "openid phone profile" + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + // Assert + Assert.Equal(Scopes.Profile, response.Scope); + } - [Fact] - public async Task ProcessSignOut_UnknownEndpointCausesAnException() + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ProcessSignIn_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); + options.EnableDegradedMode(); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.PostAsync("/signout", new OpenIddictRequest()); - }); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - Assert.Equal(SR.GetResourceString(SR.ID0024), exception.Message); - } + return default; + })); - [Fact] - public async Task ProcessSignOut_InvalidEndpointCausesAnException() - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetConfigurationEndpointUris("/signout"); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Reject(error, description, uri); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + return default; + })); + }); - return default; - })); - }); + await using var client = await server.CreateClientAsync(); - await using var client = await server.CreateClientAsync(); + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + ClientId = "Fabrikam", + RedirectUri = "http://www.fabrikam.com/path", + ResponseType = ResponseTypes.Code, + Scope = Scopes.OpenId + }); - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return client.GetAsync("/signout"); - }); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - Assert.Equal(SR.GetResourceString(SR.ID0024), exception.Message); - } - - [Theory] - [InlineData("custom_error", null, null)] - [InlineData("custom_error", "custom_description", null)] - [InlineData("custom_error", "custom_description", "custom_uri")] - [InlineData(null, "custom_description", null)] - [InlineData(null, "custom_description", "custom_uri")] - [InlineData(null, null, "custom_uri")] - [InlineData(null, null, null)] - public async Task ProcessSignOut_AllowsRejectingRequest(string error, string description, string uri) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + [Fact] + public async Task ProcessSignIn_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SignOut(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetClaim(Claims.Subject, "Bob le Magnifique"); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.Reject(error, description, uri); - - return default; - })); - }); + name = "Bob le Bricoleur" + }); - await using var client = await server.CreateClientAsync(); + context.HandleRequest(); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + return default; + })); + }); - // Assert - Assert.Equal(error ?? Errors.InvalidRequest, response.Error); - Assert.Equal(description, response.ErrorDescription); - Assert.Equal(uri, response.ErrorUri); - } + await using var client = await server.CreateClientAsync(); - [Fact] - public async Task ProcessSignOut_AllowsHandlingResponse() + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SignOut(); + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - return default; - })); + [Fact] + public async Task ProcessSignOut_UnknownEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync("/signout", new OpenIddictRequest()); + }); - context.HandleRequest(); + Assert.Equal(SR.GetResourceString(SR.ID0024), exception.Message); + } - return default; - })); - }); + [Fact] + public async Task ProcessSignOut_InvalidEndpointCausesAnException() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); + options.SetConfigurationEndpointUris("/signout"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.GetAsync("/signout"); + }); - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } + Assert.Equal(SR.GetResourceString(SR.ID0024), exception.Message); + } - protected virtual void ConfigureServices(IServiceCollection services) + [Theory] + [InlineData("custom_error", null, null)] + [InlineData("custom_error", "custom_description", null)] + [InlineData("custom_error", "custom_description", "custom_uri")] + [InlineData(null, "custom_description", null)] + [InlineData(null, "custom_description", "custom_uri")] + [InlineData(null, null, "custom_uri")] + [InlineData(null, null, null)] + public async Task ProcessSignOut_AllowsRejectingRequest(string error, string description, string uri) + { + // Arrange + await using var server = await CreateServerAsync(options => { - services.AddOpenIddict() - .AddCore(options => + options.EnableDegradedMode(); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - options.SetDefaultApplicationEntity() - .SetDefaultAuthorizationEntity() - .SetDefaultScopeEntity() - .SetDefaultTokenEntity(); + context.SignOut(); - options.Services.AddSingleton(CreateApplicationManager()) - .AddSingleton(CreateAuthorizationManager()) - .AddSingleton(CreateScopeManager()) - .AddSingleton(CreateTokenManager()); - }) + return default; + })); - .AddServer(options => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - // Enable the tested endpoints. - options.SetAuthorizationEndpointUris("/connect/authorize") - .SetConfigurationEndpointUris("/.well-known/openid-configuration") - .SetCryptographyEndpointUris("/.well-known/jwks") - .SetDeviceEndpointUris("/connect/device") - .SetIntrospectionEndpointUris("/connect/introspect") - .SetLogoutEndpointUris("/connect/logout") - .SetRevocationEndpointUris("/connect/revoke") - .SetTokenEndpointUris("/connect/token") - .SetUserinfoEndpointUris("/connect/userinfo") - .SetVerificationEndpointUris("/connect/verification"); + context.Reject(error, description, uri); - options.AllowAuthorizationCodeFlow() - .AllowClientCredentialsFlow() - .AllowDeviceCodeFlow() - .AllowHybridFlow() - .AllowImplicitFlow() - .AllowNoneFlow() - .AllowPasswordFlow() - .AllowRefreshTokenFlow(); + return default; + })); + }); - // Accept anonymous clients by default. - options.AcceptAnonymousClients(); + await using var client = await server.CreateClientAsync(); - // Disable permission enforcement by default. - options.IgnoreEndpointPermissions() - .IgnoreGrantTypePermissions() - .IgnoreResponseTypePermissions() - .IgnoreScopePermissions(); + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); - options.AddSigningCertificate( - assembly: typeof(OpenIddictServerIntegrationTests).Assembly, - resource: "OpenIddict.Server.IntegrationTests.Certificate.pfx", - password: "Owin.Security.OpenIdConnect.Server"); + // Assert + Assert.Equal(error ?? Errors.InvalidRequest, response.Error); + Assert.Equal(description, response.ErrorDescription); + Assert.Equal(uri, response.ErrorUri); + } - options.AddEncryptionCertificate( - assembly: typeof(OpenIddictServerIntegrationTests).Assembly, - resource: "OpenIddict.Server.IntegrationTests.Certificate.pfx", - password: "Owin.Security.OpenIdConnect.Server"); + [Fact] + public async Task ProcessSignOut_AllowsHandlingResponse() + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => default)); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SignOut(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => default)); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => default)); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new + { + name = "Bob le Bricoleur" + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => default)); + context.HandleRequest(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => default)); + return default; + })); + }); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => default)); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => default)); + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest()); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => default)); + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - options.AddEventHandler(builder => - builder.UseInlineHandler(context => default)); - }); - } + protected virtual void ConfigureServices(IServiceCollection services) + { + services.AddOpenIddict() + .AddCore(options => + { + options.SetDefaultApplicationEntity() + .SetDefaultAuthorizationEntity() + .SetDefaultScopeEntity() + .SetDefaultTokenEntity(); + + options.Services.AddSingleton(CreateApplicationManager()) + .AddSingleton(CreateAuthorizationManager()) + .AddSingleton(CreateScopeManager()) + .AddSingleton(CreateTokenManager()); + }) + + .AddServer(options => + { + // Enable the tested endpoints. + options.SetAuthorizationEndpointUris("/connect/authorize") + .SetConfigurationEndpointUris("/.well-known/openid-configuration") + .SetCryptographyEndpointUris("/.well-known/jwks") + .SetDeviceEndpointUris("/connect/device") + .SetIntrospectionEndpointUris("/connect/introspect") + .SetLogoutEndpointUris("/connect/logout") + .SetRevocationEndpointUris("/connect/revoke") + .SetTokenEndpointUris("/connect/token") + .SetUserinfoEndpointUris("/connect/userinfo") + .SetVerificationEndpointUris("/connect/verification"); + + options.AllowAuthorizationCodeFlow() + .AllowClientCredentialsFlow() + .AllowDeviceCodeFlow() + .AllowHybridFlow() + .AllowImplicitFlow() + .AllowNoneFlow() + .AllowPasswordFlow() + .AllowRefreshTokenFlow(); + + // Accept anonymous clients by default. + options.AcceptAnonymousClients(); + + // Disable permission enforcement by default. + options.IgnoreEndpointPermissions() + .IgnoreGrantTypePermissions() + .IgnoreResponseTypePermissions() + .IgnoreScopePermissions(); + + options.AddSigningCertificate( + assembly: typeof(OpenIddictServerIntegrationTests).Assembly, + resource: "OpenIddict.Server.IntegrationTests.Certificate.pfx", + password: "Owin.Security.OpenIdConnect.Server"); + + options.AddEncryptionCertificate( + assembly: typeof(OpenIddictServerIntegrationTests).Assembly, + resource: "OpenIddict.Server.IntegrationTests.Certificate.pfx", + password: "Owin.Security.OpenIdConnect.Server"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => default)); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => default)); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => default)); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => default)); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => default)); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => default)); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => default)); - protected abstract ValueTask CreateServerAsync( - Action? configuration = null); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => default)); - protected OpenIddictApplicationManager CreateApplicationManager( - Action>>? configuration = null) - { - var manager = new Mock>( - Mock.Of>(), - OutputHelper.ToLogger>(), - Mock.Of>(), - Mock.Of()); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => default)); + }); + } - configuration?.Invoke(manager); + protected abstract ValueTask CreateServerAsync( + Action? configuration = null); - return manager.Object; - } + protected OpenIddictApplicationManager CreateApplicationManager( + Action>>? configuration = null) + { + var manager = new Mock>( + Mock.Of>(), + OutputHelper.ToLogger>(), + Mock.Of>(), + Mock.Of()); - protected OpenIddictAuthorizationManager CreateAuthorizationManager( - Action>>? configuration = null) - { - var manager = new Mock>( - Mock.Of>(), - OutputHelper.ToLogger>(), - Mock.Of>(), - Mock.Of()); + configuration?.Invoke(manager); + + return manager.Object; + } - configuration?.Invoke(manager); + protected OpenIddictAuthorizationManager CreateAuthorizationManager( + Action>>? configuration = null) + { + var manager = new Mock>( + Mock.Of>(), + OutputHelper.ToLogger>(), + Mock.Of>(), + Mock.Of()); - return manager.Object; - } + configuration?.Invoke(manager); - protected OpenIddictScopeManager CreateScopeManager( - Action>>? configuration = null) - { - var manager = new Mock>( - Mock.Of>(), - OutputHelper.ToLogger>(), - Mock.Of>(), - Mock.Of()); + return manager.Object; + } - configuration?.Invoke(manager); + protected OpenIddictScopeManager CreateScopeManager( + Action>>? configuration = null) + { + var manager = new Mock>( + Mock.Of>(), + OutputHelper.ToLogger>(), + Mock.Of>(), + Mock.Of()); - return manager.Object; - } + configuration?.Invoke(manager); - protected OpenIddictTokenManager CreateTokenManager( - Action>>? configuration = null) - { - var manager = new Mock>( - Mock.Of>(), - OutputHelper.ToLogger>(), - Mock.Of>(), - Mock.Of()); + return manager.Object; + } - configuration?.Invoke(manager); + protected OpenIddictTokenManager CreateTokenManager( + Action>>? configuration = null) + { + var manager = new Mock>( + Mock.Of>(), + OutputHelper.ToLogger>(), + Mock.Of>(), + Mock.Of()); - return manager.Object; - } + configuration?.Invoke(manager); - public class OpenIddictApplication { } - public class OpenIddictAuthorization { } - public class OpenIddictScope { } - public class OpenIddictToken { } + return manager.Object; } + + public class OpenIddictApplication { } + public class OpenIddictAuthorization { } + public class OpenIddictScope { } + public class OpenIddictToken { } } diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs index 1fff82e7..0eaf2c9c 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTestServer.cs @@ -9,33 +9,32 @@ using System.Threading.Tasks; using Microsoft.Owin.Testing; using OpenIddict.Server.IntegrationTests; -namespace OpenIddict.Server.Owin.IntegrationTests +namespace OpenIddict.Server.Owin.IntegrationTests; + +/// +/// Represents a test host used by the server integration tests. +/// +public class OpenIddictServerOwinIntegrationTestServer : OpenIddictServerIntegrationTestServer { + public OpenIddictServerOwinIntegrationTestServer(TestServer server) + => Server = server; + /// - /// Represents a test host used by the server integration tests. + /// Gets the ASP.NET Core test server used by this instance. /// - public class OpenIddictServerOwinIntegrationTestServer : OpenIddictServerIntegrationTestServer - { - public OpenIddictServerOwinIntegrationTestServer(TestServer server) - => Server = server; + public TestServer Server { get; } - /// - /// Gets the ASP.NET Core test server used by this instance. - /// - public TestServer Server { get; } + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The caller is responsible of disposing the test client.")] + public override ValueTask CreateClientAsync() + => new ValueTask( + new OpenIddictServerIntegrationTestClient(Server.HttpClient)); - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible of disposing the test client.")] - public override ValueTask CreateClientAsync() - => new ValueTask( - new OpenIddictServerIntegrationTestClient(Server.HttpClient)); - - public override ValueTask DisposeAsync() - { - // Dispose of the underlying test server. - Server.Dispose(); + public override ValueTask DisposeAsync() + { + // Dispose of the underlying test server. + Server.Dispose(); - return default; - } + return default; } -} \ No newline at end of file +} diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Authentication.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Authentication.cs index 4bf34c3b..e2cf8b6c 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Authentication.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Authentication.cs @@ -12,51 +12,50 @@ using Xunit; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin.IntegrationTests +namespace OpenIddict.Server.Owin.IntegrationTests; + +public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests + [Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] + public async Task ExtractAuthorizationRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() { - [Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] - public async Task ExtractAuthorizationRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2028(Parameters.RequestId), response.ErrorDescription); - } - - [Fact] - public async Task ExtractAuthorizationRequest_InvalidRequestIdParameterIsRejected() + RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2028(Parameters.RequestId), response.ErrorDescription); + } + + [Fact] + public async Task ExtractAuthorizationRequest_InvalidRequestIdParameterIsRejected() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.AddDistributedMemoryCache(); - - options.UseOwin() - .EnableAuthorizationRequestCaching(); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest - { - RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.RequestId), response.ErrorDescription); - } + options.Services.AddDistributedMemoryCache(); + + options.UseOwin() + .EnableAuthorizationRequestCaching(); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest + { + RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.RequestId), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Exchange.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Exchange.cs index 38a9ae75..978a5e10 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Exchange.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Exchange.cs @@ -13,47 +13,46 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin.IntegrationTests +namespace OpenIddict.Server.Owin.IntegrationTests; + +public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests + [Fact] + public async Task ExtractTokenRequest_MultipleClientCredentialsCauseAnError() { - [Fact] - public async Task ExtractTokenRequest_MultipleClientCredentialsCauseAnError() + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - var request = context.Transaction.GetOwinRequest()!; - request.Headers["Authorization"] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; + var request = context.Transaction.GetOwinRequest()!; + request.Headers["Authorization"] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; - return default; - }); - - builder.SetOrder(int.MinValue); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/token", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" + builder.SetOrder(int.MinValue); }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); - } + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/token", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Introspection.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Introspection.cs index 6b905352..9975405f 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Introspection.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Introspection.cs @@ -13,45 +13,44 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin.IntegrationTests +namespace OpenIddict.Server.Owin.IntegrationTests; + +public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests + [Fact] + public async Task ExtractIntrospectionRequest_MultipleClientCredentialsCauseAnError() { - [Fact] - public async Task ExtractIntrospectionRequest_MultipleClientCredentialsCauseAnError() + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - var request = context.Transaction.GetOwinRequest()!; - request.Headers["Authorization"] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; + var request = context.Transaction.GetOwinRequest()!; + request.Headers["Authorization"] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; - return default; - }); - - builder.SetOrder(int.MinValue); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA" + builder.SetOrder(int.MinValue); }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); - } + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Revocation.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Revocation.cs index 158b186e..072dfd84 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Revocation.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Revocation.cs @@ -13,45 +13,44 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin.IntegrationTests +namespace OpenIddict.Server.Owin.IntegrationTests; + +public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests + [Fact] + public async Task ExtractRevocationRequest_MultipleClientCredentialsCauseAnError() { - [Fact] - public async Task ExtractRevocationRequest_MultipleClientCredentialsCauseAnError() + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - var request = context.Transaction.GetOwinRequest()!; - request.Headers["Authorization"] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; + var request = context.Transaction.GetOwinRequest()!; + request.Headers["Authorization"] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW"; - return default; - }); - - builder.SetOrder(int.MinValue); + return default; }); - }); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest - { - ClientId = "Fabrikam", - ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", - Token = "2YotnFZFEjr1zCsicMWpAA" + builder.SetOrder(int.MinValue); }); + }); - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); - } + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest + { + ClientId = "Fabrikam", + ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", + Token = "2YotnFZFEjr1zCsicMWpAA" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2087), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Session.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Session.cs index bf24a93f..33605c2d 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Session.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Session.cs @@ -12,51 +12,50 @@ using Xunit; using static OpenIddict.Abstractions.OpenIddictConstants; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin.IntegrationTests +namespace OpenIddict.Server.Owin.IntegrationTests; + +public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests + [Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] + public async Task ExtractLogoutRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() { - [Fact(Skip = "The handler responsible of rejecting such requests has not been ported yet.")] - public async Task ExtractLogoutRequest_RequestIdParameterIsRejectedWhenRequestCachingIsDisabled() + // Arrange + await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest { - // Arrange - await using var server = await CreateServerAsync(options => options.EnableDegradedMode()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2028(Parameters.RequestId), response.ErrorDescription); - } - - [Fact] - public async Task ExtractLogoutRequest_InvalidRequestIdParameterIsRejected() + RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2028(Parameters.RequestId), response.ErrorDescription); + } + + [Fact] + public async Task ExtractLogoutRequest_InvalidRequestIdParameterIsRejected() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.Services.AddDistributedMemoryCache(); - - options.UseOwin() - .EnableLogoutRequestCaching(); - }); - - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync("/connect/logout", new OpenIddictRequest - { - RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" - }); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.FormatID2052(Parameters.RequestId), response.ErrorDescription); - } + options.Services.AddDistributedMemoryCache(); + + options.UseOwin() + .EnableLogoutRequestCaching(); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync("/connect/logout", new OpenIddictRequest + { + RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7" + }); + + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.FormatID2052(Parameters.RequestId), response.ErrorDescription); } } diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs index 3d2347b5..e8bb40fa 100644 --- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs +++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs @@ -28,802 +28,801 @@ using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlers; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Owin.IntegrationTests +namespace OpenIddict.Server.Owin.IntegrationTests; + +public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests { - public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests + public OpenIddictServerOwinIntegrationTests(ITestOutputHelper outputHelper) + : base(outputHelper) { - public OpenIddictServerOwinIntegrationTests(ITestOutputHelper outputHelper) - : base(outputHelper) - { - } + } - [Fact] - public async Task ProcessAuthentication_CreationDateIsMappedToIssuedUtc() + [Fact] + public async Task ProcessAuthentication_CreationDateIsMappedToIssuedUtc() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate/properties"); + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate/properties"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetClaim(Claims.Subject, "Bob le Magnifique") - .SetCreationDate(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero)); + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - return default; - }); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetClaim(Claims.Subject, "Bob le Magnifique") + .SetCreationDate(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero)); - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest - { - AccessToken = "access_token" - }); + // Act + var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest + { + AccessToken = "access_token" + }); - // Assert - var properties = new AuthenticationProperties(response.GetParameters() - .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); - Assert.Equal(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.IssuedUtc); - } + Assert.Equal(new DateTimeOffset(2020, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.IssuedUtc); + } - [Fact] - public async Task ProcessAuthentication_ExpirationDateIsMappedToIssuedUtc() + [Fact] + public async Task ProcessAuthentication_ExpirationDateIsMappedToIssuedUtc() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetUserinfoEndpointUris("/authenticate/properties"); + options.EnableDegradedMode(); + options.SetUserinfoEndpointUris("/authenticate/properties"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - Assert.Equal("access_token", context.Token); - Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); + Assert.Equal("access_token", context.Token); + Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes); - context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(TokenTypeHints.AccessToken) - .SetExpirationDate(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero)); + context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) + .SetTokenType(TokenTypeHints.AccessToken) + .SetExpirationDate(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero)); - return default; - }); - - builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); + return default; }); + + builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest - { - AccessToken = "access_token" - }); + // Act + var response = await client.GetAsync("/authenticate/properties", new OpenIddictRequest + { + AccessToken = "access_token" + }); - // Assert - var properties = new AuthenticationProperties(response.GetParameters() - .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); + // Assert + var properties = new AuthenticationProperties(response.GetParameters() + .ToDictionary(parameter => parameter.Key, parameter => (string?) parameter.Value)); - Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); - } + Assert.Equal(new DateTimeOffset(2120, 01, 01, 00, 00, 00, TimeSpan.Zero), properties.ExpiresUtc); + } - [Fact] - public async Task ProcessChallenge_ReturnsErrorFromAuthenticationProperties() + [Fact] + public async Task ProcessChallenge_ReturnsErrorFromAuthenticationProperties() + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - options.SetTokenEndpointUris("/challenge/custom"); + options.EnableDegradedMode(); + options.SetTokenEndpointUris("/challenge/custom"); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); + await using var client = await server.CreateClientAsync(); - // Act - var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest - { - GrantType = GrantTypes.Password, - Username = "johndoe", - Password = "A3ddj3w" - }); + // Act + var response = await client.PostAsync("/challenge/custom", new OpenIddictRequest + { + GrantType = GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w" + }); + + // Assert + Assert.Equal("custom_error", response.Error); + Assert.Equal("custom_error_description", response.ErrorDescription); + Assert.Equal("custom_error_uri", response.ErrorUri); + } - // Assert - Assert.Equal("custom_error", response.Error); - Assert.Equal("custom_error_description", response.ErrorDescription); - Assert.Equal("custom_error_uri", response.ErrorUri); - } - - [Theory] - [InlineData("/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/authorize", OpenIddictServerEndpointType.Authorization)] - [InlineData("/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] - [InlineData("/connect/authorize/", OpenIddictServerEndpointType.Authorization)] - [InlineData("/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] - [InlineData("/connect/authorize/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/AUTHORIZE/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/authorize/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/AUTHORIZE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] - [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] - [InlineData("/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] - [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] - [InlineData("/.well-known/openid-configuration/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.well-known/openid-configuration/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/.well-known/jwks/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.WELL-KNOWN/JWKS/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.well-known/jwks/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/.WELL-KNOWN/JWKS/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/device", OpenIddictServerEndpointType.Device)] - [InlineData("/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] - [InlineData("/connect/device/", OpenIddictServerEndpointType.Device)] - [InlineData("/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] - [InlineData("/connect/device/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/DEVICE/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/device/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/DEVICE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/introspect", OpenIddictServerEndpointType.Introspection)] - [InlineData("/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] - [InlineData("/connect/introspect/", OpenIddictServerEndpointType.Introspection)] - [InlineData("/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] - [InlineData("/connect/introspect/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/INTROSPECT/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/introspect/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/INTROSPECT/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/logout", OpenIddictServerEndpointType.Logout)] - [InlineData("/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] - [InlineData("/connect/logout/", OpenIddictServerEndpointType.Logout)] - [InlineData("/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] - [InlineData("/connect/logout/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/LOGOUT/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/logout/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/LOGOUT/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/revoke", OpenIddictServerEndpointType.Revocation)] - [InlineData("/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] - [InlineData("/connect/revoke/", OpenIddictServerEndpointType.Revocation)] - [InlineData("/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] - [InlineData("/connect/revoke/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/REVOKE/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/revoke/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/REVOKE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/token", OpenIddictServerEndpointType.Token)] - [InlineData("/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] - [InlineData("/connect/token/", OpenIddictServerEndpointType.Token)] - [InlineData("/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] - [InlineData("/connect/token/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/TOKEN/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/token/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/TOKEN/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/connect/userinfo/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/USERINFO/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/userinfo/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/USERINFO/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/verification", OpenIddictServerEndpointType.Verification)] - [InlineData("/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] - [InlineData("/connect/verification/", OpenIddictServerEndpointType.Verification)] - [InlineData("/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] - [InlineData("/connect/verification/subpath", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/VERIFICATION/SUBPATH", OpenIddictServerEndpointType.Unknown)] - [InlineData("/connect/verification/subpath/", OpenIddictServerEndpointType.Unknown)] - [InlineData("/CONNECT/VERIFICATION/SUBPATH/", OpenIddictServerEndpointType.Unknown)] - public async Task ProcessRequest_MatchesCorrespondingRelativeEndpoint(string path, OpenIddictServerEndpointType type) + [Theory] + [InlineData("/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/authorize", OpenIddictServerEndpointType.Authorization)] + [InlineData("/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] + [InlineData("/connect/authorize/", OpenIddictServerEndpointType.Authorization)] + [InlineData("/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] + [InlineData("/connect/authorize/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/AUTHORIZE/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/authorize/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/AUTHORIZE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] + [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] + [InlineData("/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] + [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] + [InlineData("/.well-known/openid-configuration/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.well-known/openid-configuration/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.WELL-KNOWN/OPENID-CONFIGURATION/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/.well-known/jwks/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.WELL-KNOWN/JWKS/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.well-known/jwks/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/.WELL-KNOWN/JWKS/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/device", OpenIddictServerEndpointType.Device)] + [InlineData("/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] + [InlineData("/connect/device/", OpenIddictServerEndpointType.Device)] + [InlineData("/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] + [InlineData("/connect/device/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/DEVICE/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/device/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/DEVICE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/introspect", OpenIddictServerEndpointType.Introspection)] + [InlineData("/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] + [InlineData("/connect/introspect/", OpenIddictServerEndpointType.Introspection)] + [InlineData("/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] + [InlineData("/connect/introspect/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/INTROSPECT/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/introspect/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/INTROSPECT/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/logout", OpenIddictServerEndpointType.Logout)] + [InlineData("/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] + [InlineData("/connect/logout/", OpenIddictServerEndpointType.Logout)] + [InlineData("/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] + [InlineData("/connect/logout/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/LOGOUT/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/logout/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/LOGOUT/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/revoke", OpenIddictServerEndpointType.Revocation)] + [InlineData("/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] + [InlineData("/connect/revoke/", OpenIddictServerEndpointType.Revocation)] + [InlineData("/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] + [InlineData("/connect/revoke/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/REVOKE/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/revoke/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/REVOKE/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/token", OpenIddictServerEndpointType.Token)] + [InlineData("/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] + [InlineData("/connect/token/", OpenIddictServerEndpointType.Token)] + [InlineData("/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] + [InlineData("/connect/token/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/TOKEN/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/token/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/TOKEN/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/connect/userinfo/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/USERINFO/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/userinfo/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/USERINFO/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/verification", OpenIddictServerEndpointType.Verification)] + [InlineData("/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] + [InlineData("/connect/verification/", OpenIddictServerEndpointType.Verification)] + [InlineData("/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] + [InlineData("/connect/verification/subpath", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/VERIFICATION/SUBPATH", OpenIddictServerEndpointType.Unknown)] + [InlineData("/connect/verification/subpath/", OpenIddictServerEndpointType.Unknown)] + [InlineData("/CONNECT/VERIFICATION/SUBPATH/", OpenIddictServerEndpointType.Unknown)] + public async Task ProcessRequest_MatchesCorrespondingRelativeEndpoint(string path, OpenIddictServerEndpointType type) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - // Assert - Assert.Equal(type, context.EndpointType); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + // Assert + Assert.Equal(type, context.EndpointType); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); - - // Act - await client.PostAsync(path, new OpenIddictRequest()); - } - - [Theory] - [InlineData("https://localhost/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:443/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST/CONNECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST/CONNECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:443/connect", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:443/connect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/authorize", OpenIddictServerEndpointType.Authorization)] - [InlineData("HTTPS://LOCALHOST/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] - [InlineData("https://localhost/connect/authorize/", OpenIddictServerEndpointType.Authorization)] - [InlineData("HTTPS://LOCALHOST/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] - [InlineData("https://localhost:443/connect/authorize", OpenIddictServerEndpointType.Authorization)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] - [InlineData("https://localhost:443/connect/authorize/", OpenIddictServerEndpointType.Authorization)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] - [InlineData("https://fabrikam.com/connect/authorize", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/authorize/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/authorize", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/authorize/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] - [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] - [InlineData("https://localhost/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] - [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] - [InlineData("https://localhost:443/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] - [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] - [InlineData("https://localhost:443/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] - [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] - [InlineData("https://fabrikam.com/.well-known/openid-configuration", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/.well-known/openid-configuration/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/.well-known/openid-configuration", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/.well-known/openid-configuration/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] - [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] - [InlineData("https://localhost/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("https://localhost:443/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] - [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] - [InlineData("https://localhost:443/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] - [InlineData("https://fabrikam.com/.well-known/jwks", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/.well-known/jwks/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/.well-known/jwks", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/.well-known/jwks/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/device", OpenIddictServerEndpointType.Device)] - [InlineData("HTTPS://LOCALHOST/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] - [InlineData("https://localhost/connect/device/", OpenIddictServerEndpointType.Device)] - [InlineData("HTTPS://LOCALHOST/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] - [InlineData("https://localhost:443/connect/device", OpenIddictServerEndpointType.Device)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] - [InlineData("https://localhost:443/connect/device/", OpenIddictServerEndpointType.Device)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] - [InlineData("https://fabrikam.com/connect/device", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/DEVICE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/device/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/DEVICE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/device", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/DEVICE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/device/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/DEVICE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/introspect", OpenIddictServerEndpointType.Introspection)] - [InlineData("HTTPS://LOCALHOST/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] - [InlineData("https://localhost/connect/introspect/", OpenIddictServerEndpointType.Introspection)] - [InlineData("HTTPS://LOCALHOST/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] - [InlineData("https://localhost:443/connect/introspect", OpenIddictServerEndpointType.Introspection)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] - [InlineData("https://localhost:443/connect/introspect/", OpenIddictServerEndpointType.Introspection)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] - [InlineData("https://fabrikam.com/connect/introspect", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/introspect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/introspect", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/introspect/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/logout", OpenIddictServerEndpointType.Logout)] - [InlineData("HTTPS://LOCALHOST/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] - [InlineData("https://localhost/connect/logout/", OpenIddictServerEndpointType.Logout)] - [InlineData("HTTPS://LOCALHOST/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] - [InlineData("https://localhost:443/connect/logout", OpenIddictServerEndpointType.Logout)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] - [InlineData("https://localhost:443/connect/logout/", OpenIddictServerEndpointType.Logout)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] - [InlineData("https://fabrikam.com/connect/logout", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/LOGOUT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/logout/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/logout", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/LOGOUT", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/logout/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/revoke", OpenIddictServerEndpointType.Revocation)] - [InlineData("HTTPS://LOCALHOST/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] - [InlineData("https://localhost/connect/revoke/", OpenIddictServerEndpointType.Revocation)] - [InlineData("HTTPS://LOCALHOST/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] - [InlineData("https://localhost:443/connect/revoke", OpenIddictServerEndpointType.Revocation)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] - [InlineData("https://localhost:443/connect/revoke/", OpenIddictServerEndpointType.Revocation)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] - [InlineData("https://fabrikam.com/connect/revoke", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/REVOKE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/revoke/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/REVOKE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/revoke", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/REVOKE", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/revoke/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/REVOKE/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/token", OpenIddictServerEndpointType.Token)] - [InlineData("HTTPS://LOCALHOST/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] - [InlineData("https://localhost/connect/token/", OpenIddictServerEndpointType.Token)] - [InlineData("HTTPS://LOCALHOST/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] - [InlineData("https://localhost:443/connect/token", OpenIddictServerEndpointType.Token)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] - [InlineData("https://localhost:443/connect/token/", OpenIddictServerEndpointType.Token)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] - [InlineData("https://fabrikam.com/connect/token", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/TOKEN", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/token/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/TOKEN/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/token", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/TOKEN", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/token/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/TOKEN/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] - [InlineData("HTTPS://LOCALHOST/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] - [InlineData("https://localhost/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("HTTPS://LOCALHOST/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("https://localhost:443/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] - [InlineData("https://localhost:443/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] - [InlineData("https://fabrikam.com/connect/userinfo", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/USERINFO", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/userinfo/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/USERINFO/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/userinfo", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/USERINFO", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/userinfo/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/USERINFO/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost/connect/verification", OpenIddictServerEndpointType.Verification)] - [InlineData("HTTPS://LOCALHOST/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] - [InlineData("https://localhost/connect/verification/", OpenIddictServerEndpointType.Verification)] - [InlineData("HTTPS://LOCALHOST/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] - [InlineData("https://localhost:443/connect/verification", OpenIddictServerEndpointType.Verification)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] - [InlineData("https://localhost:443/connect/verification/", OpenIddictServerEndpointType.Verification)] - [InlineData("HTTPS://LOCALHOST:443/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] - [InlineData("https://fabrikam.com/connect/verification", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://fabrikam.com/connect/verification/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://FABRIKAM.COM/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/verification", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Unknown)] - [InlineData("https://localhost:8888/connect/verification/", OpenIddictServerEndpointType.Unknown)] - [InlineData("HTTPS://LOCALHOST:8888/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Unknown)] - public async Task ProcessRequest_MatchesCorrespondingAbsoluteEndpoint(string path, OpenIddictServerEndpointType type) + await using var client = await server.CreateClientAsync(); + + // Act + await client.PostAsync(path, new OpenIddictRequest()); + } + + [Theory] + [InlineData("https://localhost/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:443/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST/CONNECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST/CONNECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:443/connect", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:443/connect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/authorize", OpenIddictServerEndpointType.Authorization)] + [InlineData("HTTPS://LOCALHOST/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] + [InlineData("https://localhost/connect/authorize/", OpenIddictServerEndpointType.Authorization)] + [InlineData("HTTPS://LOCALHOST/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] + [InlineData("https://localhost:443/connect/authorize", OpenIddictServerEndpointType.Authorization)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Authorization)] + [InlineData("https://localhost:443/connect/authorize/", OpenIddictServerEndpointType.Authorization)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Authorization)] + [InlineData("https://fabrikam.com/connect/authorize", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/authorize/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/authorize", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/AUTHORIZE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/authorize/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/AUTHORIZE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] + [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] + [InlineData("https://localhost/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] + [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] + [InlineData("https://localhost:443/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] + [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Configuration)] + [InlineData("https://localhost:443/.well-known/openid-configuration/", OpenIddictServerEndpointType.Configuration)] + [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Configuration)] + [InlineData("https://fabrikam.com/.well-known/openid-configuration", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/.well-known/openid-configuration/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/.well-known/openid-configuration", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/OPENID-CONFIGURATION", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/.well-known/openid-configuration/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/OPENID-CONFIGURATION/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] + [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] + [InlineData("https://localhost/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("HTTPS://LOCALHOST/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("https://localhost:443/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] + [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Cryptography)] + [InlineData("https://localhost:443/.well-known/jwks/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("HTTPS://LOCALHOST:443/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Cryptography)] + [InlineData("https://fabrikam.com/.well-known/jwks", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/.well-known/jwks/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/.well-known/jwks", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/JWKS", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/.well-known/jwks/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/.WELL-KNOWN/JWKS/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/device", OpenIddictServerEndpointType.Device)] + [InlineData("HTTPS://LOCALHOST/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] + [InlineData("https://localhost/connect/device/", OpenIddictServerEndpointType.Device)] + [InlineData("HTTPS://LOCALHOST/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] + [InlineData("https://localhost:443/connect/device", OpenIddictServerEndpointType.Device)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/DEVICE", OpenIddictServerEndpointType.Device)] + [InlineData("https://localhost:443/connect/device/", OpenIddictServerEndpointType.Device)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/DEVICE/", OpenIddictServerEndpointType.Device)] + [InlineData("https://fabrikam.com/connect/device", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/DEVICE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/device/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/DEVICE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/device", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/DEVICE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/device/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/DEVICE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/introspect", OpenIddictServerEndpointType.Introspection)] + [InlineData("HTTPS://LOCALHOST/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] + [InlineData("https://localhost/connect/introspect/", OpenIddictServerEndpointType.Introspection)] + [InlineData("HTTPS://LOCALHOST/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] + [InlineData("https://localhost:443/connect/introspect", OpenIddictServerEndpointType.Introspection)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Introspection)] + [InlineData("https://localhost:443/connect/introspect/", OpenIddictServerEndpointType.Introspection)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Introspection)] + [InlineData("https://fabrikam.com/connect/introspect", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/introspect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/introspect", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/INTROSPECT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/introspect/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/INTROSPECT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/logout", OpenIddictServerEndpointType.Logout)] + [InlineData("HTTPS://LOCALHOST/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] + [InlineData("https://localhost/connect/logout/", OpenIddictServerEndpointType.Logout)] + [InlineData("HTTPS://LOCALHOST/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] + [InlineData("https://localhost:443/connect/logout", OpenIddictServerEndpointType.Logout)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/LOGOUT", OpenIddictServerEndpointType.Logout)] + [InlineData("https://localhost:443/connect/logout/", OpenIddictServerEndpointType.Logout)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Logout)] + [InlineData("https://fabrikam.com/connect/logout", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/LOGOUT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/logout/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/logout", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/LOGOUT", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/logout/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/LOGOUT/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/revoke", OpenIddictServerEndpointType.Revocation)] + [InlineData("HTTPS://LOCALHOST/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] + [InlineData("https://localhost/connect/revoke/", OpenIddictServerEndpointType.Revocation)] + [InlineData("HTTPS://LOCALHOST/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] + [InlineData("https://localhost:443/connect/revoke", OpenIddictServerEndpointType.Revocation)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/REVOKE", OpenIddictServerEndpointType.Revocation)] + [InlineData("https://localhost:443/connect/revoke/", OpenIddictServerEndpointType.Revocation)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/REVOKE/", OpenIddictServerEndpointType.Revocation)] + [InlineData("https://fabrikam.com/connect/revoke", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/REVOKE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/revoke/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/REVOKE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/revoke", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/REVOKE", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/revoke/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/REVOKE/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/token", OpenIddictServerEndpointType.Token)] + [InlineData("HTTPS://LOCALHOST/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] + [InlineData("https://localhost/connect/token/", OpenIddictServerEndpointType.Token)] + [InlineData("HTTPS://LOCALHOST/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] + [InlineData("https://localhost:443/connect/token", OpenIddictServerEndpointType.Token)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/TOKEN", OpenIddictServerEndpointType.Token)] + [InlineData("https://localhost:443/connect/token/", OpenIddictServerEndpointType.Token)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/TOKEN/", OpenIddictServerEndpointType.Token)] + [InlineData("https://fabrikam.com/connect/token", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/TOKEN", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/token/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/TOKEN/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/token", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/TOKEN", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/token/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/TOKEN/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] + [InlineData("HTTPS://LOCALHOST/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] + [InlineData("https://localhost/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("HTTPS://LOCALHOST/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("https://localhost:443/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/USERINFO", OpenIddictServerEndpointType.Userinfo)] + [InlineData("https://localhost:443/connect/userinfo/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/USERINFO/", OpenIddictServerEndpointType.Userinfo)] + [InlineData("https://fabrikam.com/connect/userinfo", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/USERINFO", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/userinfo/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/USERINFO/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/userinfo", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/USERINFO", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/userinfo/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/USERINFO/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost/connect/verification", OpenIddictServerEndpointType.Verification)] + [InlineData("HTTPS://LOCALHOST/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] + [InlineData("https://localhost/connect/verification/", OpenIddictServerEndpointType.Verification)] + [InlineData("HTTPS://LOCALHOST/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] + [InlineData("https://localhost:443/connect/verification", OpenIddictServerEndpointType.Verification)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Verification)] + [InlineData("https://localhost:443/connect/verification/", OpenIddictServerEndpointType.Verification)] + [InlineData("HTTPS://LOCALHOST:443/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Verification)] + [InlineData("https://fabrikam.com/connect/verification", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://fabrikam.com/connect/verification/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://FABRIKAM.COM/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/verification", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/VERIFICATION", OpenIddictServerEndpointType.Unknown)] + [InlineData("https://localhost:8888/connect/verification/", OpenIddictServerEndpointType.Unknown)] + [InlineData("HTTPS://LOCALHOST:8888/CONNECT/VERIFICATION/", OpenIddictServerEndpointType.Unknown)] + public async Task ProcessRequest_MatchesCorrespondingAbsoluteEndpoint(string path, OpenIddictServerEndpointType type) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); - - options.SetAuthorizationEndpointUris("https://localhost/connect/authorize") - .SetConfigurationEndpointUris("https://localhost/.well-known/openid-configuration") - .SetCryptographyEndpointUris("https://localhost/.well-known/jwks") - .SetDeviceEndpointUris("https://localhost/connect/device") - .SetIntrospectionEndpointUris("https://localhost/connect/introspect") - .SetLogoutEndpointUris("https://localhost/connect/logout") - .SetRevocationEndpointUris("https://localhost/connect/revoke") - .SetTokenEndpointUris("https://localhost/connect/token") - .SetUserinfoEndpointUris("https://localhost/connect/userinfo") - .SetVerificationEndpointUris("https://localhost/connect/verification"); - - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.EnableDegradedMode(); + + options.SetAuthorizationEndpointUris("https://localhost/connect/authorize") + .SetConfigurationEndpointUris("https://localhost/.well-known/openid-configuration") + .SetCryptographyEndpointUris("https://localhost/.well-known/jwks") + .SetDeviceEndpointUris("https://localhost/connect/device") + .SetIntrospectionEndpointUris("https://localhost/connect/introspect") + .SetLogoutEndpointUris("https://localhost/connect/logout") + .SetRevocationEndpointUris("https://localhost/connect/revoke") + .SetTokenEndpointUris("https://localhost/connect/token") + .SetUserinfoEndpointUris("https://localhost/connect/userinfo") + .SetVerificationEndpointUris("https://localhost/connect/verification"); + + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - // Assert - Assert.Equal(type, context.EndpointType); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + // Assert + Assert.Equal(type, context.EndpointType); - return default; - })); - }); + return default; + })); + }); - await using var client = await server.CreateClientAsync(); - - // Act - await client.PostAsync(path, new OpenIddictRequest()); - } - - [Theory] - [InlineData("/custom/connect/authorize", OpenIddictServerEndpointType.Authorization)] - [InlineData("/custom/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] - [InlineData("/custom/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] - [InlineData("/custom/connect/device", OpenIddictServerEndpointType.Device)] - [InlineData("/custom/connect/custom", OpenIddictServerEndpointType.Unknown)] - [InlineData("/custom/connect/introspect", OpenIddictServerEndpointType.Introspection)] - [InlineData("/custom/connect/logout", OpenIddictServerEndpointType.Logout)] - [InlineData("/custom/connect/revoke", OpenIddictServerEndpointType.Revocation)] - [InlineData("/custom/connect/token", OpenIddictServerEndpointType.Token)] - [InlineData("/custom/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] - [InlineData("/custom/connect/verification", OpenIddictServerEndpointType.Verification)] - public async Task ProcessRequest_AllowsOverridingEndpoint(string address, OpenIddictServerEndpointType type) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + await using var client = await server.CreateClientAsync(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + // Act + await client.PostAsync(path, new OpenIddictRequest()); + } - return default; - })); + [Theory] + [InlineData("/custom/connect/authorize", OpenIddictServerEndpointType.Authorization)] + [InlineData("/custom/.well-known/openid-configuration", OpenIddictServerEndpointType.Configuration)] + [InlineData("/custom/.well-known/jwks", OpenIddictServerEndpointType.Cryptography)] + [InlineData("/custom/connect/device", OpenIddictServerEndpointType.Device)] + [InlineData("/custom/connect/custom", OpenIddictServerEndpointType.Unknown)] + [InlineData("/custom/connect/introspect", OpenIddictServerEndpointType.Introspection)] + [InlineData("/custom/connect/logout", OpenIddictServerEndpointType.Logout)] + [InlineData("/custom/connect/revoke", OpenIddictServerEndpointType.Revocation)] + [InlineData("/custom/connect/token", OpenIddictServerEndpointType.Token)] + [InlineData("/custom/connect/userinfo", OpenIddictServerEndpointType.Userinfo)] + [InlineData("/custom/connect/verification", OpenIddictServerEndpointType.Verification)] + public async Task ProcessRequest_AllowsOverridingEndpoint(string address, OpenIddictServerEndpointType type) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.SkipRequest(); + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.SkipRequest(); - return default; - })); + return default; + })); - options.AddEventHandler(builder => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - builder.UseInlineHandler(context => - { - // Act - context.EndpointType = type; + context.SkipRequest(); - // Assert - Assert.Equal(type, context.EndpointType); + return default; + })); - return default; - }); + options.AddEventHandler(builder => + { + builder.UseInlineHandler(context => + { + // Act + context.EndpointType = type; - builder.SetOrder(InferEndpointType.Descriptor.Order + 500); + // Assert + Assert.Equal(type, context.EndpointType); + + return default; }); + + builder.SetOrder(InferEndpointType.Descriptor.Order + 500); }); + }); - await using var client = await server.CreateClientAsync(); - - // Act - await client.PostAsync(address, new OpenIddictRequest()); - } - - [Theory] - [InlineData("/.well-known/openid-configuration")] - [InlineData("/.well-known/jwks")] - [InlineData("/connect/authorize")] - [InlineData("/connect/device")] - [InlineData("/connect/introspect")] - [InlineData("/connect/logout")] - [InlineData("/connect/revoke")] - [InlineData("/connect/token")] - [InlineData("/connect/userinfo")] - [InlineData("/connect/verification")] - public async Task ProcessRequest_RejectsInsecureHttpRequests(string address) - { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + await using var client = await server.CreateClientAsync(); - options.UseOwin() - .Configure(options => options.DisableTransportSecurityRequirement = false); - }); + // Act + await client.PostAsync(address, new OpenIddictRequest()); + } - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync(address, new OpenIddictRequest()); - - // Assert - Assert.Equal(Errors.InvalidRequest, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2083), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2083), response.ErrorUri); - } - - [Theory] - [InlineData("/.well-known/openid-configuration")] - [InlineData("/.well-known/jwks")] - [InlineData("/custom")] - [InlineData("/connect/authorize")] - [InlineData("/connect/device")] - [InlineData("/connect/introspect")] - [InlineData("/connect/logout")] - [InlineData("/connect/revoke")] - [InlineData("/connect/token")] - [InlineData("/connect/userinfo")] - [InlineData("/connect/verification")] - public async Task ProcessRequest_AllowsHandlingResponse(string address) + [Theory] + [InlineData("/.well-known/openid-configuration")] + [InlineData("/.well-known/jwks")] + [InlineData("/connect/authorize")] + [InlineData("/connect/device")] + [InlineData("/connect/introspect")] + [InlineData("/connect/logout")] + [InlineData("/connect/revoke")] + [InlineData("/connect/token")] + [InlineData("/connect/userinfo")] + [InlineData("/connect/verification")] + public async Task ProcessRequest_RejectsInsecureHttpRequests(string address) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => - { - context.Transaction.SetProperty("custom_response", new - { - name = "Bob le Bricoleur" - }); + options.UseOwin() + .Configure(options => options.DisableTransportSecurityRequirement = false); + }); - context.HandleRequest(); + await using var client = await server.CreateClientAsync(); - return default; - })); - }); + // Act + var response = await client.PostAsync(address, new OpenIddictRequest()); - await using var client = await server.CreateClientAsync(); - - // Act - var response = await client.PostAsync(address, new OpenIddictRequest()); - - // Assert - Assert.Equal("Bob le Bricoleur", (string?) response["name"]); - } - - [Theory] - [InlineData("/.well-known/openid-configuration")] - [InlineData("/.well-known/jwks")] - [InlineData("/custom")] - [InlineData("/connect/authorize")] - [InlineData("/connect/device")] - [InlineData("/connect/introspect")] - [InlineData("/connect/logout")] - [InlineData("/connect/revoke")] - [InlineData("/connect/token")] - [InlineData("/connect/userinfo")] - [InlineData("/connect/verification")] - public async Task ProcessRequest_AllowsSkippingHandler(string address) + // Assert + Assert.Equal(Errors.InvalidRequest, response.Error); + Assert.Equal(SR.GetResourceString(SR.ID2083), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2083), response.ErrorUri); + } + + [Theory] + [InlineData("/.well-known/openid-configuration")] + [InlineData("/.well-known/jwks")] + [InlineData("/custom")] + [InlineData("/connect/authorize")] + [InlineData("/connect/device")] + [InlineData("/connect/introspect")] + [InlineData("/connect/logout")] + [InlineData("/connect/revoke")] + [InlineData("/connect/token")] + [InlineData("/connect/userinfo")] + [InlineData("/connect/verification")] + public async Task ProcessRequest_AllowsHandlingResponse(string address) + { + // Arrange + await using var server = await CreateServerAsync(options => { - // Arrange - await using var server = await CreateServerAsync(options => - { - options.EnableDegradedMode(); + options.EnableDegradedMode(); - options.AddEventHandler(builder => - builder.UseInlineHandler(context => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => + { + context.Transaction.SetProperty("custom_response", new { - context.SkipRequest(); + name = "Bob le Bricoleur" + }); - return default; - })); - }); + context.HandleRequest(); - await using var client = await server.CreateClientAsync(); + return default; + })); + }); - // Act - var response = await client.PostAsync(address, new OpenIddictRequest()); + await using var client = await server.CreateClientAsync(); - // Assert - Assert.Equal("Bob le Magnifique", (string?) response["name"]); - } + // Act + var response = await client.PostAsync(address, new OpenIddictRequest()); - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", - Justification = "The caller is responsible of disposing the test server.")] - protected override ValueTask CreateServerAsync(Action? configuration = null) - { - var services = new ServiceCollection(); - ConfigureServices(services); + // Assert + Assert.Equal("Bob le Bricoleur", (string?) response["name"]); + } - services.AddLogging(options => options.AddXUnit(OutputHelper)); + [Theory] + [InlineData("/.well-known/openid-configuration")] + [InlineData("/.well-known/jwks")] + [InlineData("/custom")] + [InlineData("/connect/authorize")] + [InlineData("/connect/device")] + [InlineData("/connect/introspect")] + [InlineData("/connect/logout")] + [InlineData("/connect/revoke")] + [InlineData("/connect/token")] + [InlineData("/connect/userinfo")] + [InlineData("/connect/verification")] + public async Task ProcessRequest_AllowsSkippingHandler(string address) + { + // Arrange + await using var server = await CreateServerAsync(options => + { + options.EnableDegradedMode(); - services.AddOpenIddict() - .AddServer(options => + options.AddEventHandler(builder => + builder.UseInlineHandler(context => { - // Disable the transport security requirement during testing. - options.UseOwin() - .DisableTransportSecurityRequirement(); + context.SkipRequest(); - configuration?.Invoke(options); - }); + return default; + })); + }); + + await using var client = await server.CreateClientAsync(); + + // Act + var response = await client.PostAsync(address, new OpenIddictRequest()); - var provider = services.BuildServiceProvider(); + // Assert + Assert.Equal("Bob le Magnifique", (string?) response["name"]); + } + + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", + Justification = "The caller is responsible of disposing the test server.")] + protected override ValueTask CreateServerAsync(Action? configuration = null) + { + var services = new ServiceCollection(); + ConfigureServices(services); - var server = TestServer.Create(app => + services.AddLogging(options => options.AddXUnit(OutputHelper)); + + services.AddOpenIddict() + .AddServer(options => { - app.Use(async (context, next) => - { - using var scope = provider.CreateScope(); + // Disable the transport security requirement during testing. + options.UseOwin() + .DisableTransportSecurityRequirement(); - context.Set(typeof(IServiceProvider).FullName, scope.ServiceProvider); + configuration?.Invoke(options); + }); - try - { - await next(); - } + var provider = services.BuildServiceProvider(); - finally - { - context.Environment.Remove(typeof(IServiceProvider).FullName); - } - }); + var server = TestServer.Create(app => + { + app.Use(async (context, next) => + { + using var scope = provider.CreateScope(); + + context.Set(typeof(IServiceProvider).FullName, scope.ServiceProvider); - app.Use(async (context, next) => + try { await next(); + } - var transaction = context.Get(typeof(OpenIddictServerTransaction).FullName); - var response = transaction?.GetProperty("custom_response"); - if (response is not null) - { - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(response)); - } - }); + finally + { + context.Environment.Remove(typeof(IServiceProvider).FullName); + } + }); - app.UseOpenIddictServer(); + app.Use(async (context, next) => + { + await next(); - app.Use(async (context, next) => + var transaction = context.Get(typeof(OpenIddictServerTransaction).FullName); + var response = transaction?.GetProperty("custom_response"); + if (response is not null) { - if (context.Request.Path == new PathString("/signin")) - { - var identity = new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType); - identity.AddClaim(Claims.Subject, "Bob le Bricoleur"); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(response)); + } + }); - context.Authentication.SignIn(identity); - return; - } + app.UseOpenIddictServer(); - else if (context.Request.Path == new PathString("/signout")) - { - context.Authentication.SignOut(OpenIddictServerOwinDefaults.AuthenticationType); - return; - } + app.Use(async (context, next) => + { + if (context.Request.Path == new PathString("/signin")) + { + var identity = new ClaimsIdentity(OpenIddictServerOwinDefaults.AuthenticationType); + identity.AddClaim(Claims.Subject, "Bob le Bricoleur"); - else if (context.Request.Path == new PathString("/challenge")) - { - context.Authentication.Challenge(OpenIddictServerOwinDefaults.AuthenticationType); - return; - } + context.Authentication.SignIn(identity); + return; + } + + else if (context.Request.Path == new PathString("/signout")) + { + context.Authentication.SignOut(OpenIddictServerOwinDefaults.AuthenticationType); + return; + } + + else if (context.Request.Path == new PathString("/challenge")) + { + context.Authentication.Challenge(OpenIddictServerOwinDefaults.AuthenticationType); + return; + } - else if (context.Request.Path == new PathString("/challenge/custom")) + else if (context.Request.Path == new PathString("/challenge/custom")) + { + var properties = new AuthenticationProperties(new Dictionary { - var properties = new AuthenticationProperties(new Dictionary - { - [OpenIddictServerOwinConstants.Properties.Error] = "custom_error", - [OpenIddictServerOwinConstants.Properties.ErrorDescription] = "custom_error_description", - [OpenIddictServerOwinConstants.Properties.ErrorUri] = "custom_error_uri" - }); - - context.Authentication.Challenge(properties, OpenIddictServerOwinDefaults.AuthenticationType); - return; - } + [OpenIddictServerOwinConstants.Properties.Error] = "custom_error", + [OpenIddictServerOwinConstants.Properties.ErrorDescription] = "custom_error_description", + [OpenIddictServerOwinConstants.Properties.ErrorUri] = "custom_error_uri" + }); + + context.Authentication.Challenge(properties, OpenIddictServerOwinDefaults.AuthenticationType); + return; + } - else if (context.Request.Path == new PathString("/authenticate")) + else if (context.Request.Path == new PathString("/authenticate")) + { + var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); + if (result?.Identity is null) { - var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); - if (result?.Identity is null) - { - return; - } - - var claims = result.Identity.Claims.GroupBy(claim => claim.Type) - .Select(group => new KeyValuePair( - group.Key, group.Select(claim => claim.Value).ToArray())); - - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(claims))); return; } - else if (context.Request.Path == new PathString("/authenticate/properties")) + var claims = result.Identity.Claims.GroupBy(claim => claim.Type) + .Select(group => new KeyValuePair( + group.Key, group.Select(claim => claim.Value).ToArray())); + + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(claims))); + return; + } + + else if (context.Request.Path == new PathString("/authenticate/properties")) + { + var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); + if (result?.Properties is null) { - var result = await context.Authentication.AuthenticateAsync(OpenIddictServerOwinDefaults.AuthenticationType); - if (result?.Properties is null) - { - return; - } - - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(result.Properties.Dictionary))); return; } - await next(); - }); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(new OpenIddictResponse(result.Properties.Dictionary))); + return; + } + + await next(); + }); - app.Run(context => + app.Run(context => + { + context.Response.ContentType = "application/json"; + return context.Response.WriteAsync(JsonSerializer.Serialize(new { - context.Response.ContentType = "application/json"; - return context.Response.WriteAsync(JsonSerializer.Serialize(new - { - name = "Bob le Magnifique" - })); - }); + name = "Bob le Magnifique" + })); }); + }); - return new ValueTask( - new OpenIddictServerOwinIntegrationTestServer(server)); - } + return new ValueTask( + new OpenIddictServerOwinIntegrationTestServer(server)); } } diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs index 101f7308..e9f5161e 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs @@ -14,1979 +14,1978 @@ using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using SR = OpenIddict.Abstractions.OpenIddictResources; -namespace OpenIddict.Server.Tests +namespace OpenIddict.Server.Tests; + +public class OpenIddictServerBuilderTests { - public class OpenIddictServerBuilderTests + [Fact] + public void Constructor_ThrowsAnExceptionForNullServices() { - [Fact] - public void Constructor_ThrowsAnExceptionForNullServices() - { - // Arrange - var services = (IServiceCollection) null!; + // Arrange + var services = (IServiceCollection) null!; - // Act and assert - var exception = Assert.Throws(() => new OpenIddictServerBuilder(services)); + // Act and assert + var exception = Assert.Throws(() => new OpenIddictServerBuilder(services)); - Assert.Equal("services", exception.ParamName); - } + Assert.Equal("services", exception.ParamName); + } - [Fact] - public void AddEventHandler_ThrowsAnExceptionWhenConfigurationIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddEventHandler_ThrowsAnExceptionWhenConfigurationIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.AddEventHandler(configuration: null!)); - Assert.Equal("configuration", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => builder.AddEventHandler(configuration: null!)); + Assert.Equal("configuration", exception.ParamName); + } - [Fact] - public void AddEventHandler_ThrowsAnExceptionWhenDescriptorIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddEventHandler_ThrowsAnExceptionWhenDescriptorIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.AddEventHandler(descriptor: null!)); - Assert.Equal("descriptor", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => builder.AddEventHandler(descriptor: null!)); + Assert.Equal("descriptor", exception.ParamName); + } - [Fact] - public void AddEventHandler_HandlerIsAttached() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.AddEventHandler(x => - { - x.UseSingletonHandler(); - }); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(CustomHandler)); - } - - [Fact] - public void AddEventHandler_HandlerInstanceIsRegistered() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.AddEventHandler(x => - { - x.UseSingletonHandler(new CustomHandler()); - }); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(CustomHandler) && - service.ImplementationInstance?.GetType() == typeof(CustomHandler) && - service.Lifetime == ServiceLifetime.Singleton); - } - - [Fact] - public void AddEventHandler_SingletonHandlerIsRegisteredAsASingleton() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.AddEventHandler(x => - { - x.UseSingletonHandler(); - }); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(CustomHandler) && - service.Lifetime == ServiceLifetime.Singleton); - } - - [Fact] - public void AddEventHandler_ScopedHandlerIsRegisteredAsScoped() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.AddEventHandler(x => - { - x.UseScopedHandler(); - }); - - // Assert - Assert.Contains(services, service => - service.ServiceType == typeof(CustomHandler) && - service.Lifetime == ServiceLifetime.Scoped); - } - - [Fact] - public void AddEncryptionCredentials_ThrowsExceptionWhenCredentialsAreNull() + [Fact] + public void AddEventHandler_HandlerIsAttached() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.AddEventHandler(x => { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + x.UseSingletonHandler(); + }); - // Act and assert - var exception = Assert.Throws(() => builder.AddEncryptionCredentials(credentials: null!)); - Assert.Equal("credentials", exception.ParamName); - } + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(CustomHandler)); + } + + [Fact] + public void AddEventHandler_HandlerInstanceIsRegistered() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void AddEncryptionKey_ThrowsExceptionWhenKeyIsNull() + // Act + builder.AddEventHandler(x => { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + x.UseSingletonHandler(new CustomHandler()); + }); - // Act and assert - var exception = Assert.Throws(() => builder.AddEncryptionKey(key: null!)); - Assert.Equal("key", exception.ParamName); - } + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(CustomHandler) && + service.ImplementationInstance?.GetType() == typeof(CustomHandler) && + service.Lifetime == ServiceLifetime.Singleton); + } - [Fact] - public void AddEncryptionKey_ThrowsExceptionWhenAsymmetricKeyPrivateKeyIsMissing() + [Fact] + public void AddEventHandler_SingletonHandlerIsRegisteredAsASingleton() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.AddEventHandler(x => { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - var key = Mock.Of(key => key.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist); - - // Act and assert - var exception = Assert.Throws(() => builder.AddEncryptionKey(key)); - Assert.Equal("The asymmetric encryption key doesn't contain the required private key.", exception.Message); - } - - [Fact] - public void AddEncryptionKey_EncryptingKeyIsCorrectlyAdded() + x.UseSingletonHandler(); + }); + + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(CustomHandler) && + service.Lifetime == ServiceLifetime.Singleton); + } + + [Fact] + public void AddEventHandler_ScopedHandlerIsRegisteredAsScoped() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.AddEventHandler(x => { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + x.UseScopedHandler(); + }); - var key = Mock.Of(mock => mock.KeySize == 256 && mock.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)); + // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(CustomHandler) && + service.Lifetime == ServiceLifetime.Scoped); + } - // Act - builder.AddEncryptionKey(key); + [Fact] + public void AddEncryptionCredentials_ThrowsExceptionWhenCredentialsAreNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - var options = GetOptions(services); + // Act and assert + var exception = Assert.Throws(() => builder.AddEncryptionCredentials(credentials: null!)); + Assert.Equal("credentials", exception.ParamName); + } - // Assert - Assert.Same(key, options.EncryptionCredentials[0].Key); - } + [Fact] + public void AddEncryptionKey_ThrowsExceptionWhenKeyIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void AddEncryptionKey_ThrowsExceptionWhenSymmetricKeyIsTooShort() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var key = Mock.Of(mock => mock.KeySize == 128 && mock.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)); - var exception = Assert.Throws(() => builder.AddEncryptionKey(key)); - Assert.Equal(SR.FormatID0283(256, 128), exception.Message); - } - - [Fact] - public void AddEncryptionKey_ThrowsExceptionWhenSymmetricKeyIsTooLong() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var key = Mock.Of(mock => mock.KeySize == 384 && mock.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)); - var exception = Assert.Throws(() => builder.AddEncryptionKey(key)); - Assert.Equal(SR.FormatID0283(256, 384), exception.Message); - } - - [Fact] - public void RemoveEventHandler_ThrowsAnExceptionWhenDescriptorIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Act and assert + var exception = Assert.Throws(() => builder.AddEncryptionKey(key: null!)); + Assert.Equal("key", exception.ParamName); + } + + [Fact] + public void AddEncryptionKey_ThrowsExceptionWhenAsymmetricKeyPrivateKeyIsMissing() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + var key = Mock.Of(key => key.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist); + + // Act and assert + var exception = Assert.Throws(() => builder.AddEncryptionKey(key)); + Assert.Equal("The asymmetric encryption key doesn't contain the required private key.", exception.Message); + } - // Act and assert - var exception = Assert.Throws(() => builder.RemoveEventHandler(descriptor: null!)); - Assert.Equal("descriptor", exception.ParamName); - } + [Fact] + public void AddEncryptionKey_EncryptingKeyIsCorrectlyAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void RemoveEventHandler_RemovesService() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var key = Mock.Of(mock => mock.KeySize == 256 && mock.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)); - OpenIddictServerHandlerDescriptor descriptor = OpenIddictServerHandlerDescriptor.CreateBuilder().UseSingletonHandler().Build(); - builder.AddEventHandler(descriptor); + // Act + builder.AddEncryptionKey(key); - // Act - builder.RemoveEventHandler(descriptor); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.DoesNotContain(services, x => x.ServiceType == descriptor.ServiceDescriptor.ServiceType); - Assert.DoesNotContain(options.Handlers, x => x.ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType); - } + // Assert + Assert.Same(key, options.EncryptionCredentials[0].Key); + } - [Fact] - public void Configure_DelegateIsCorrectlyRegistered() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - var configuration = new Action(options => { }); - - // Act - builder.Configure(configuration); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(IConfigureOptions) && - service.ImplementationInstance is ConfigureNamedOptions options && - options.Action == configuration && string.IsNullOrEmpty(options.Name)); - } - - [Fact] - public void Configure_ThrowsAnExceptionWhenConfigurationIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddEncryptionKey_ThrowsExceptionWhenSymmetricKeyIsTooShort() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var key = Mock.Of(mock => mock.KeySize == 128 && mock.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)); + var exception = Assert.Throws(() => builder.AddEncryptionKey(key)); + Assert.Equal(SR.FormatID0283(256, 128), exception.Message); + } - // Act and assert - var exception = Assert.Throws(() => builder.Configure(configuration: null!)); - Assert.Equal("configuration", exception.ParamName); - } + [Fact] + public void AddEncryptionKey_ThrowsExceptionWhenSymmetricKeyIsTooLong() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var key = Mock.Of(mock => mock.KeySize == 384 && mock.IsSupportedAlgorithm(SecurityAlgorithms.Aes256KW)); + var exception = Assert.Throws(() => builder.AddEncryptionKey(key)); + Assert.Equal(SR.FormatID0283(256, 384), exception.Message); + } - [Fact] - public void AddDevelopmentEncryptionCertificate_ThrowsAnExceptionForNullSubject() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void RemoveEventHandler_ThrowsAnExceptionWhenDescriptorIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.RemoveEventHandler(descriptor: null!)); + Assert.Equal("descriptor", exception.ParamName); + } + + [Fact] + public void RemoveEventHandler_RemovesService() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + OpenIddictServerHandlerDescriptor descriptor = OpenIddictServerHandlerDescriptor.CreateBuilder().UseSingletonHandler().Build(); + builder.AddEventHandler(descriptor); + + // Act + builder.RemoveEventHandler(descriptor); + var options = GetOptions(services); + + // Assert + Assert.DoesNotContain(services, x => x.ServiceType == descriptor.ServiceDescriptor.ServiceType); + Assert.DoesNotContain(options.Handlers, x => x.ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType); + } + + [Fact] + public void Configure_DelegateIsCorrectlyRegistered() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + var configuration = new Action(options => { }); + + // Act + builder.Configure(configuration); + + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(IConfigureOptions) && + service.ImplementationInstance is ConfigureNamedOptions options && + options.Action == configuration && string.IsNullOrEmpty(options.Name)); + } + + [Fact] + public void Configure_ThrowsAnExceptionWhenConfigurationIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.AddDevelopmentEncryptionCertificate(subject: null!)); - Assert.Equal("subject", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => builder.Configure(configuration: null!)); + Assert.Equal("configuration", exception.ParamName); + } + + [Fact] + public void AddDevelopmentEncryptionCertificate_ThrowsAnExceptionForNullSubject() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.AddDevelopmentEncryptionCertificate(subject: null!)); + Assert.Equal("subject", exception.ParamName); + } #if SUPPORTS_CERTIFICATE_GENERATION - [Fact] - public void AddDevelopmentEncryptionCertificate_CanGenerateCertificate() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddDevelopmentEncryptionCertificate_CanGenerateCertificate() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AddDevelopmentEncryptionCertificate(); + // Act + builder.AddDevelopmentEncryptionCertificate(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Single(options.EncryptionCredentials); - Assert.Equal(SecurityAlgorithms.RsaOAEP, options.EncryptionCredentials[0].Alg); - Assert.Equal(SecurityAlgorithms.Aes256CbcHmacSha512, options.EncryptionCredentials[0].Enc); - Assert.NotNull(options.EncryptionCredentials[0].Key.KeyId); - } + // Assert + Assert.Single(options.EncryptionCredentials); + Assert.Equal(SecurityAlgorithms.RsaOAEP, options.EncryptionCredentials[0].Alg); + Assert.Equal(SecurityAlgorithms.Aes256CbcHmacSha512, options.EncryptionCredentials[0].Enc); + Assert.NotNull(options.EncryptionCredentials[0].Key.KeyId); + } #else - [Fact] - public void AddDevelopmentEncryptionCertificate_ThrowsAnExceptionOnUnsupportedPlatforms() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddDevelopmentEncryptionCertificate_ThrowsAnExceptionOnUnsupportedPlatforms() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.AddDevelopmentEncryptionCertificate( - subject: new X500DistinguishedName("CN=" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)))); + // Act and assert + var exception = Assert.Throws(() => builder.AddDevelopmentEncryptionCertificate( + subject: new X500DistinguishedName("CN=" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)))); - Assert.Equal("X.509 certificate generation is not supported on this platform.", exception.Message); - } + Assert.Equal("X.509 certificate generation is not supported on this platform.", exception.Message); + } #endif - [Fact] - public void AddDevelopmentSigningCertificate_ThrowsAnExceptionForNullSubject() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddDevelopmentSigningCertificate_ThrowsAnExceptionForNullSubject() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(delegate - { - builder.AddDevelopmentSigningCertificate(subject: null!); - }); + // Act and assert + var exception = Assert.Throws(delegate + { + builder.AddDevelopmentSigningCertificate(subject: null!); + }); - Assert.Equal("subject", exception.ParamName); - } + Assert.Equal("subject", exception.ParamName); + } #if SUPPORTS_CERTIFICATE_GENERATION - [Fact] - public void AddDevelopmentSigningCertificate_CanGenerateCertificate() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddDevelopmentSigningCertificate_CanGenerateCertificate() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AddDevelopmentSigningCertificate(); + // Act + builder.AddDevelopmentSigningCertificate(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Single(options.SigningCredentials); - Assert.Equal(SecurityAlgorithms.RsaSha256, options.SigningCredentials[0].Algorithm); - Assert.NotNull(options.SigningCredentials[0].Kid); - } + // Assert + Assert.Single(options.SigningCredentials); + Assert.Equal(SecurityAlgorithms.RsaSha256, options.SigningCredentials[0].Algorithm); + Assert.NotNull(options.SigningCredentials[0].Kid); + } #else - [Fact] - public void AddDevelopmentSigningCertificate_ThrowsAnExceptionOnUnsupportedPlatforms() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddDevelopmentSigningCertificate_ThrowsAnExceptionOnUnsupportedPlatforms() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.AddDevelopmentSigningCertificate( - subject: new X500DistinguishedName("CN=" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)))); + // Act and assert + var exception = Assert.Throws(() => builder.AddDevelopmentSigningCertificate( + subject: new X500DistinguishedName("CN=" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)))); - Assert.Equal("X.509 certificate generation is not supported on this platform.", exception.Message); - } + Assert.Equal("X.509 certificate generation is not supported on this platform.", exception.Message); + } #endif - [Fact] - public void AddEphemeralSigningKey_SigningKeyIsCorrectlyAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddEphemeralSigningKey_SigningKeyIsCorrectlyAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AddEphemeralSigningKey(); + // Act + builder.AddEphemeralSigningKey(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Single(options.SigningCredentials); - } + // Assert + Assert.Single(options.SigningCredentials); + } - [Theory] - [InlineData(SecurityAlgorithms.RsaSha256)] - [InlineData(SecurityAlgorithms.RsaSha384)] - [InlineData(SecurityAlgorithms.RsaSha512)] + [Theory] + [InlineData(SecurityAlgorithms.RsaSha256)] + [InlineData(SecurityAlgorithms.RsaSha384)] + [InlineData(SecurityAlgorithms.RsaSha512)] #if SUPPORTS_ECDSA - [InlineData(SecurityAlgorithms.EcdsaSha256)] - [InlineData(SecurityAlgorithms.EcdsaSha384)] - [InlineData(SecurityAlgorithms.EcdsaSha512)] + [InlineData(SecurityAlgorithms.EcdsaSha256)] + [InlineData(SecurityAlgorithms.EcdsaSha384)] + [InlineData(SecurityAlgorithms.EcdsaSha512)] #endif - public void AddEphemeralSigningKey_SigningCredentialsUseSpecifiedAlgorithm(string algorithm) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + public void AddEphemeralSigningKey_SigningCredentialsUseSpecifiedAlgorithm(string algorithm) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AddEphemeralSigningKey(algorithm); + // Act + builder.AddEphemeralSigningKey(algorithm); - var options = GetOptions(services); - var credentials = options.SigningCredentials[0]; + var options = GetOptions(services); + var credentials = options.SigningCredentials[0]; - // Assert - Assert.Equal(algorithm, credentials.Algorithm); - } + // Assert + Assert.Equal(algorithm, credentials.Algorithm); + } - [Fact] - public void AddSigningKey_ThrowsExceptionWhenKeyIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddSigningKey_ThrowsExceptionWhenKeyIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.AddSigningKey(key: null!)); - Assert.Equal("key", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => builder.AddSigningKey(key: null!)); + Assert.Equal("key", exception.ParamName); + } - [Fact] - public void AddSigningKey_ThrowsExceptionWhenAsymmetricKeyPrivateKeyIsMissing() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - var key = Mock.Of(key => key.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist); - - // Act and assert - var exception = Assert.Throws(() => builder.AddSigningKey(key)); - Assert.Equal("The asymmetric signing key doesn't contain the required private key.", exception.Message); - } - - [Theory] - [InlineData(SecurityAlgorithms.HmacSha256)] - [InlineData(SecurityAlgorithms.RsaSha256)] + [Fact] + public void AddSigningKey_ThrowsExceptionWhenAsymmetricKeyPrivateKeyIsMissing() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + var key = Mock.Of(key => key.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist); + + // Act and assert + var exception = Assert.Throws(() => builder.AddSigningKey(key)); + Assert.Equal("The asymmetric signing key doesn't contain the required private key.", exception.Message); + } + + [Theory] + [InlineData(SecurityAlgorithms.HmacSha256)] + [InlineData(SecurityAlgorithms.RsaSha256)] #if SUPPORTS_ECDSA - [InlineData(SecurityAlgorithms.EcdsaSha256)] - [InlineData(SecurityAlgorithms.EcdsaSha384)] - [InlineData(SecurityAlgorithms.EcdsaSha512)] + [InlineData(SecurityAlgorithms.EcdsaSha256)] + [InlineData(SecurityAlgorithms.EcdsaSha384)] + [InlineData(SecurityAlgorithms.EcdsaSha512)] #endif - public void AddSigningKey_SigningKeyIsCorrectlyAdded(string algorithm) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + public void AddSigningKey_SigningKeyIsCorrectlyAdded(string algorithm) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - var key = Mock.Of(mock => mock.IsSupportedAlgorithm(algorithm)); + var key = Mock.Of(mock => mock.IsSupportedAlgorithm(algorithm)); - // Act - builder.AddSigningKey(key); + // Act + builder.AddSigningKey(key); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Same(key, options.SigningCredentials[0].Key); - } + // Assert + Assert.Same(key, options.SigningCredentials[0].Key); + } - [Fact] - public void AddSigningCertificate_SigningKeyIsCorrectlyAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddSigningCertificate_SigningKeyIsCorrectlyAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AddSigningCertificate( - assembly: typeof(OpenIddictServerBuilderTests).GetTypeInfo().Assembly, - resource: "OpenIddict.Server.Tests.Certificate.pfx", - password: "OpenIddict"); + // Act + builder.AddSigningCertificate( + assembly: typeof(OpenIddictServerBuilderTests).GetTypeInfo().Assembly, + resource: "OpenIddict.Server.Tests.Certificate.pfx", + password: "OpenIddict"); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.IsType(options.SigningCredentials[0].Key); - } + // Assert + Assert.IsType(options.SigningCredentials[0].Key); + } - [Fact] - public void AllowAuthorizationCodeFlow_CodeFlowIsAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AllowAuthorizationCodeFlow_CodeFlowIsAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AllowAuthorizationCodeFlow(); + // Act + builder.AllowAuthorizationCodeFlow(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(CodeChallengeMethods.Sha256, options.CodeChallengeMethods); + // Assert + Assert.Contains(CodeChallengeMethods.Sha256, options.CodeChallengeMethods); - Assert.Contains(GrantTypes.AuthorizationCode, options.GrantTypes); + Assert.Contains(GrantTypes.AuthorizationCode, options.GrantTypes); - Assert.Contains(ResponseModes.FormPost, options.ResponseModes); - Assert.Contains(ResponseModes.Fragment, options.ResponseModes); - Assert.Contains(ResponseModes.Query, options.ResponseModes); + Assert.Contains(ResponseModes.FormPost, options.ResponseModes); + Assert.Contains(ResponseModes.Fragment, options.ResponseModes); + Assert.Contains(ResponseModes.Query, options.ResponseModes); - Assert.Contains(ResponseTypes.Code, options.ResponseTypes); - } + Assert.Contains(ResponseTypes.Code, options.ResponseTypes); + } - [Fact] - public void AllowClientCredentialsFlow_ClientCredentialsFlowIsAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AllowClientCredentialsFlow_ClientCredentialsFlowIsAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AllowClientCredentialsFlow(); + // Act + builder.AllowClientCredentialsFlow(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(GrantTypes.ClientCredentials, options.GrantTypes); - } + // Assert + Assert.Contains(GrantTypes.ClientCredentials, options.GrantTypes); + } - [Theory] - [InlineData(null)] - [InlineData("")] - public void AllowCustomFlow_ThrowsAnExceptionForType(string type) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Theory] + [InlineData(null)] + [InlineData("")] + public void AllowCustomFlow_ThrowsAnExceptionForType(string type) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.AllowCustomFlow(type)); + // Act and assert + var exception = Assert.Throws(() => builder.AllowCustomFlow(type)); - Assert.Equal("type", exception.ParamName); - Assert.Contains("The grant type cannot be null or empty.", exception.Message); - } + Assert.Equal("type", exception.ParamName); + Assert.Contains("The grant type cannot be null or empty.", exception.Message); + } - [Fact] - public void AllowCustomFlow_CustomFlowIsAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AllowCustomFlow_CustomFlowIsAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); + // Act + builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.GrantTypes); - } + // Assert + Assert.Contains("urn:ietf:params:oauth:grant-type:custom_grant", options.GrantTypes); + } - [Fact] - public void AddDeviceCodeFlow_DeviceFlowIsAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AddDeviceCodeFlow_DeviceFlowIsAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AllowDeviceCodeFlow(); + // Act + builder.AllowDeviceCodeFlow(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(GrantTypes.DeviceCode, options.GrantTypes); - } + // Assert + Assert.Contains(GrantTypes.DeviceCode, options.GrantTypes); + } - [Fact] - public void AllowHybridFlow_HybridFlowIsAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AllowHybridFlow_HybridFlowIsAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AllowHybridFlow(); + // Act + builder.AllowHybridFlow(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(CodeChallengeMethods.Sha256, options.CodeChallengeMethods); + // Assert + Assert.Contains(CodeChallengeMethods.Sha256, options.CodeChallengeMethods); - Assert.Contains(GrantTypes.AuthorizationCode, options.GrantTypes); - Assert.Contains(GrantTypes.Implicit, options.GrantTypes); + Assert.Contains(GrantTypes.AuthorizationCode, options.GrantTypes); + Assert.Contains(GrantTypes.Implicit, options.GrantTypes); - Assert.Contains(ResponseModes.FormPost, options.ResponseModes); - Assert.Contains(ResponseModes.Fragment, options.ResponseModes); + Assert.Contains(ResponseModes.FormPost, options.ResponseModes); + Assert.Contains(ResponseModes.Fragment, options.ResponseModes); - Assert.Contains(ResponseTypes.Code + ' ' + ResponseTypes.IdToken, options.ResponseTypes); - Assert.Contains(ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token, options.ResponseTypes); - Assert.Contains(ResponseTypes.Code + ' ' + ResponseTypes.Token, options.ResponseTypes); - } + Assert.Contains(ResponseTypes.Code + ' ' + ResponseTypes.IdToken, options.ResponseTypes); + Assert.Contains(ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token, options.ResponseTypes); + Assert.Contains(ResponseTypes.Code + ' ' + ResponseTypes.Token, options.ResponseTypes); + } - [Fact] - public void AllowImplicitFlow_ImplicitFlowIsAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AllowImplicitFlow_ImplicitFlowIsAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AllowImplicitFlow(); + // Act + builder.AllowImplicitFlow(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(GrantTypes.Implicit, options.GrantTypes); + // Assert + Assert.Contains(GrantTypes.Implicit, options.GrantTypes); - Assert.Contains(ResponseModes.FormPost, options.ResponseModes); - Assert.Contains(ResponseModes.Fragment, options.ResponseModes); + Assert.Contains(ResponseModes.FormPost, options.ResponseModes); + Assert.Contains(ResponseModes.Fragment, options.ResponseModes); - Assert.Contains(ResponseTypes.IdToken, options.ResponseTypes); - Assert.Contains(ResponseTypes.IdToken + ' ' + ResponseTypes.Token, options.ResponseTypes); - Assert.Contains(ResponseTypes.Token, options.ResponseTypes); - } + Assert.Contains(ResponseTypes.IdToken, options.ResponseTypes); + Assert.Contains(ResponseTypes.IdToken + ' ' + ResponseTypes.Token, options.ResponseTypes); + Assert.Contains(ResponseTypes.Token, options.ResponseTypes); + } - [Fact] - public void AllowPasswordFlow_PasswordFlowIsAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AllowPasswordFlow_PasswordFlowIsAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AllowPasswordFlow(); + // Act + builder.AllowPasswordFlow(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(GrantTypes.Password, options.GrantTypes); - } + // Assert + Assert.Contains(GrantTypes.Password, options.GrantTypes); + } - [Fact] - public void AllowRefreshTokenFlow_RefreshTokenFlowIsAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AllowRefreshTokenFlow_RefreshTokenFlowIsAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AllowRefreshTokenFlow(); + // Act + builder.AllowRefreshTokenFlow(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(GrantTypes.RefreshToken, options.GrantTypes); - } + // Assert + Assert.Contains(GrantTypes.RefreshToken, options.GrantTypes); + } - [Fact] - public void DisableAccessTokenEncryption_AccessTokenEncryptionIsDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void DisableAccessTokenEncryption_AccessTokenEncryptionIsDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.DisableAccessTokenEncryption(); + // Act + builder.DisableAccessTokenEncryption(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.True(options.DisableAccessTokenEncryption); - } + // Assert + Assert.True(options.DisableAccessTokenEncryption); + } - [Fact] - public void DisableAuthorizationStorage_AuthorizationStorageIsDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void DisableAuthorizationStorage_AuthorizationStorageIsDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.DisableAuthorizationStorage(); + // Act + builder.DisableAuthorizationStorage(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.True(options.DisableAuthorizationStorage); - } + // Assert + Assert.True(options.DisableAuthorizationStorage); + } - [Fact] - public void DisableRollingRefreshTokens_RollingRefreshTokensAreDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void DisableRollingRefreshTokens_RollingRefreshTokensAreDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.DisableRollingRefreshTokens(); + // Act + builder.DisableRollingRefreshTokens(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.True(options.DisableRollingRefreshTokens); - } + // Assert + Assert.True(options.DisableRollingRefreshTokens); + } - [Fact] - public void DisableScopeValidation_ScopeValidationIsDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void DisableScopeValidation_ScopeValidationIsDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.DisableScopeValidation(); + // Act + builder.DisableScopeValidation(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.True(options.DisableScopeValidation); - } + // Assert + Assert.True(options.DisableScopeValidation); + } - [Fact] - public void DisableSlidingRefreshTokenExpiration_SlidingExpirationIsDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void DisableSlidingRefreshTokenExpiration_SlidingExpirationIsDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.DisableSlidingRefreshTokenExpiration(); + // Act + builder.DisableSlidingRefreshTokenExpiration(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.True(options.DisableSlidingRefreshTokenExpiration); - } + // Assert + Assert.True(options.DisableSlidingRefreshTokenExpiration); + } - [Fact] - public void DisableTokenStorage_TokenStorageIsDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void DisableTokenStorage_TokenStorageIsDisabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.DisableTokenStorage(); + // Act + builder.DisableTokenStorage(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.True(options.DisableTokenStorage); - } + // Assert + Assert.True(options.DisableTokenStorage); + } - [Fact] - public void RequireProofKeyForCodeExchange_PkceIsEnforced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void RequireProofKeyForCodeExchange_PkceIsEnforced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.RequireProofKeyForCodeExchange(); + // Act + builder.RequireProofKeyForCodeExchange(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.True(options.RequireProofKeyForCodeExchange); - } + // Assert + Assert.True(options.RequireProofKeyForCodeExchange); + } - [Fact] - public void SetAuthorizationEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetAuthorizationEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetAuthorizationEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetAuthorizationEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetAuthorizationEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetAuthorizationEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetAuthorizationEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetAuthorizationEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetAuthorizationEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetAuthorizationEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetAuthorizationEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetAuthorizationEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetAuthorizationEndpointUris(Array.Empty()); + // Act and assert + var exception = Assert.Throws(() => builder.SetAuthorizationEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - var options = GetOptions(services); + [Theory] + [InlineData(@"C:\")] + public void SetAuthorizationEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetAuthorizationEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Assert - Assert.Empty(options.AuthorizationEndpointUris); - } + [Theory] + [InlineData("~/path")] + public void SetAuthorizationEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetAuthorizationEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - [Fact] - public void SetAuthorizationEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetAuthorizationEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetAuthorizationEndpointUris("http://localhost/endpoint-path"); + // Act + builder.SetAuthorizationEndpointUris(Array.Empty()); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.AuthorizationEndpointUris); - } + // Assert + Assert.Empty(options.AuthorizationEndpointUris); + } - [Fact] - public void SetConfigurationEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetAuthorizationEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetConfigurationEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + // Act + builder.SetAuthorizationEndpointUris("http://localhost/endpoint-path"); - [Fact] - public void SetConfigurationEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetConfigurationEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetConfigurationEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetConfigurationEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetConfigurationEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetConfigurationEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetConfigurationEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.SetConfigurationEndpointUris(Array.Empty()); + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.AuthorizationEndpointUris); + } - var options = GetOptions(services); + [Fact] + public void SetConfigurationEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Empty(options.ConfigurationEndpointUris); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetConfigurationEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetConfigurationEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetConfigurationEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetConfigurationEndpointUris("http://localhost/endpoint-path"); + // Act and assert + var exception = Assert.Throws(() => builder.SetConfigurationEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - var options = GetOptions(services); + [Theory] + [InlineData(@"C:\")] + public void SetConfigurationEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetConfigurationEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.ConfigurationEndpointUris); - } + [Theory] + [InlineData("~/path")] + public void SetConfigurationEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetConfigurationEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - [Fact] - public void SetCryptographyEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetConfigurationEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetCryptographyEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + // Act + builder.SetConfigurationEndpointUris(Array.Empty()); - [Fact] - public void SetCryptographyEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetCryptographyEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetCryptographyEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetCryptographyEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetCryptographyEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetCryptographyEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetCryptographyEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.SetCryptographyEndpointUris(Array.Empty()); + // Assert + Assert.Empty(options.ConfigurationEndpointUris); + } - var options = GetOptions(services); + [Fact] + public void SetConfigurationEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Empty(options.CryptographyEndpointUris); - } + // Act + builder.SetConfigurationEndpointUris("http://localhost/endpoint-path"); - [Fact] - public void SetCryptographyEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.SetCryptographyEndpointUris("http://localhost/endpoint-path"); + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.ConfigurationEndpointUris); + } - var options = GetOptions(services); + [Fact] + public void SetCryptographyEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.CryptographyEndpointUris); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetCryptographyEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetDeviceEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetCryptographyEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetDeviceEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetCryptographyEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetDeviceEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetDeviceEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetDeviceEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetDeviceEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetDeviceEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetDeviceEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetDeviceEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Theory] + [InlineData(@"C:\")] + public void SetCryptographyEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetCryptographyEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Act - builder.SetDeviceEndpointUris(Array.Empty()); + [Theory] + [InlineData("~/path")] + public void SetCryptographyEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetCryptographyEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - var options = GetOptions(services); + [Fact] + public void SetCryptographyEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Empty(options.DeviceEndpointUris); - } + // Act + builder.SetCryptographyEndpointUris(Array.Empty()); - [Fact] - public void SetDeviceEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.SetDeviceEndpointUris("http://localhost/endpoint-path"); + // Assert + Assert.Empty(options.CryptographyEndpointUris); + } - var options = GetOptions(services); + [Fact] + public void SetCryptographyEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.DeviceEndpointUris); - } + // Act + builder.SetCryptographyEndpointUris("http://localhost/endpoint-path"); - [Fact] - public void SetIntrospectionEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetIntrospectionEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.CryptographyEndpointUris); + } - [Fact] - public void SetIntrospectionEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetIntrospectionEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetIntrospectionEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetIntrospectionEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetIntrospectionEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetIntrospectionEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetIntrospectionEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetDeviceEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetIntrospectionEndpointUris(Array.Empty()); + // Act and assert + var exception = Assert.Throws(() => builder.SetDeviceEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - var options = GetOptions(services); + [Fact] + public void SetDeviceEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Empty(options.IntrospectionEndpointUris); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetDeviceEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetIntrospectionEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Theory] + [InlineData(@"C:\")] + public void SetDeviceEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetDeviceEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Act - builder.SetIntrospectionEndpointUris("http://localhost/endpoint-path"); + [Theory] + [InlineData("~/path")] + public void SetDeviceEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetDeviceEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - var options = GetOptions(services); + [Fact] + public void SetDeviceEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.IntrospectionEndpointUris); - } + // Act + builder.SetDeviceEndpointUris(Array.Empty()); - [Fact] - public void SetLogoutEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetLogoutEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + // Assert + Assert.Empty(options.DeviceEndpointUris); + } - [Fact] - public void SetLogoutEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetLogoutEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetLogoutEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetLogoutEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetLogoutEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetLogoutEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetLogoutEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetDeviceEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetLogoutEndpointUris(Array.Empty()); + // Act + builder.SetDeviceEndpointUris("http://localhost/endpoint-path"); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Empty(options.LogoutEndpointUris); - } + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.DeviceEndpointUris); + } - [Fact] - public void SetLogoutEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetIntrospectionEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetLogoutEndpointUris("http://localhost/endpoint-path"); + // Act and assert + var exception = Assert.Throws(() => builder.SetIntrospectionEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - var options = GetOptions(services); + [Fact] + public void SetIntrospectionEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.LogoutEndpointUris); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetIntrospectionEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetRevocationEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Theory] + [InlineData(@"C:\")] + public void SetIntrospectionEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetIntrospectionEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Act and assert - var exception = Assert.Throws(() => builder.SetRevocationEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + [Theory] + [InlineData("~/path")] + public void SetIntrospectionEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetIntrospectionEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - [Fact] - public void SetRevocationEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetRevocationEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetRevocationEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetRevocationEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetRevocationEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetRevocationEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetRevocationEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetIntrospectionEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetRevocationEndpointUris(Array.Empty()); + // Act + builder.SetIntrospectionEndpointUris(Array.Empty()); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Empty(options.RevocationEndpointUris); - } + // Assert + Assert.Empty(options.IntrospectionEndpointUris); + } - [Fact] - public void SetRevocationEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetIntrospectionEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetRevocationEndpointUris("http://localhost/endpoint-path"); + // Act + builder.SetIntrospectionEndpointUris("http://localhost/endpoint-path"); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.RevocationEndpointUris); - } + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.IntrospectionEndpointUris); + } - [Fact] - public void SetTokenEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetLogoutEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetTokenEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetLogoutEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetTokenEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetTokenEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetTokenEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetTokenEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetTokenEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetTokenEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetTokenEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetLogoutEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetTokenEndpointUris(Array.Empty()); + // Act and assert + var exception = Assert.Throws(() => builder.SetLogoutEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - var options = GetOptions(services); + [Theory] + [InlineData(@"C:\")] + public void SetLogoutEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetLogoutEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Assert - Assert.Empty(options.TokenEndpointUris); - } + [Theory] + [InlineData("~/path")] + public void SetLogoutEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetLogoutEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - [Fact] - public void SetTokenEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetLogoutEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetTokenEndpointUris("http://localhost/endpoint-path"); + // Act + builder.SetLogoutEndpointUris(Array.Empty()); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.TokenEndpointUris); - } + // Assert + Assert.Empty(options.LogoutEndpointUris); + } - [Fact] - public void SetUserinfoEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetLogoutEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetUserinfoEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + // Act + builder.SetLogoutEndpointUris("http://localhost/endpoint-path"); - [Fact] - public void SetUserinfoEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetUserinfoEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetUserinfoEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetUserinfoEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetUserinfoEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetUserinfoEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetUserinfoEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.SetUserinfoEndpointUris(Array.Empty()); + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.LogoutEndpointUris); + } - var options = GetOptions(services); + [Fact] + public void SetRevocationEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Empty(options.UserinfoEndpointUris); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetRevocationEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetUserinfoEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetRevocationEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetUserinfoEndpointUris("http://localhost/endpoint-path"); + // Act and assert + var exception = Assert.Throws(() => builder.SetRevocationEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - var options = GetOptions(services); + [Theory] + [InlineData(@"C:\")] + public void SetRevocationEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetRevocationEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.UserinfoEndpointUris); - } + [Theory] + [InlineData("~/path")] + public void SetRevocationEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetRevocationEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - [Fact] - public void SetVerificationEndpointUris_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetRevocationEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetVerificationEndpointUris(addresses: (null as Uri[])!)); - Assert.Equal("addresses", exception.ParamName); - } + // Act + builder.SetRevocationEndpointUris(Array.Empty()); - [Fact] - public void SetVerificationEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetVerificationEndpointUris(addresses: (null as string[])!)); - Assert.Equal("addresses", exception.ParamName); - } - - [Theory] - [InlineData(@"C:\")] - public void SetVerificationEndpointUris_ThrowsExceptionForMalformedUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetVerificationEndpointUris(new Uri(uri))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); - } - - [Theory] - [InlineData("~/path")] - public void SetVerificationEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.SetVerificationEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); - Assert.Equal("addresses", exception.ParamName); - Assert.Contains(SR.FormatID0081("~"), exception.Message); - } - - [Fact] - public void SetVerificationEndpointUris_ClearsUris() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.SetVerificationEndpointUris(Array.Empty()); + // Assert + Assert.Empty(options.RevocationEndpointUris); + } - var options = GetOptions(services); + [Fact] + public void SetRevocationEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Empty(options.VerificationEndpointUris); - } + // Act + builder.SetRevocationEndpointUris("http://localhost/endpoint-path"); - [Fact] - public void SetVerificationEndpointUris_AddsUri() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.SetVerificationEndpointUris("http://localhost/endpoint-path"); + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.RevocationEndpointUris); + } - var options = GetOptions(services); + [Fact] + public void SetTokenEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Contains(new Uri("http://localhost/endpoint-path"), options.VerificationEndpointUris); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetTokenEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void AcceptAnonymousClients_ClientIdentificationIsOptional() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetTokenEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.AcceptAnonymousClients(); + // Act and assert + var exception = Assert.Throws(() => builder.SetTokenEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - var options = GetOptions(services); + [Theory] + [InlineData(@"C:\")] + public void SetTokenEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetTokenEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Assert - Assert.True(options.AcceptAnonymousClients); - } + [Theory] + [InlineData("~/path")] + public void SetTokenEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetTokenEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - [Fact] - public void SetAccessTokenLifetime_DefaultAccessTokenLifetimeIsReplaced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetTokenEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(42)); + // Act + builder.SetTokenEndpointUris(Array.Empty()); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.AccessTokenLifetime); - } + // Assert + Assert.Empty(options.TokenEndpointUris); + } - [Fact] - public void SetAccessTokenLifetime_AccessTokenLifetimeCanBeSetToNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetTokenEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetAccessTokenLifetime(null); + // Act + builder.SetTokenEndpointUris("http://localhost/endpoint-path"); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Null(options.AccessTokenLifetime); - } + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.TokenEndpointUris); + } - [Fact] - public void SetAuthorizationCodeLifetime_DefaultAuthorizationCodeLifetimeIsReplaced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetUserinfoEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42)); + // Act and assert + var exception = Assert.Throws(() => builder.SetUserinfoEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - var options = GetOptions(services); + [Fact] + public void SetUserinfoEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.AuthorizationCodeLifetime); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetUserinfoEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetAuthorizationCodeLifetime_AuthorizationCodeLifetimeCanBeSetToNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Theory] + [InlineData(@"C:\")] + public void SetUserinfoEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetUserinfoEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Act - builder.SetAuthorizationCodeLifetime(null); + [Theory] + [InlineData("~/path")] + public void SetUserinfoEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetUserinfoEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - var options = GetOptions(services); + [Fact] + public void SetUserinfoEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Null(options.AuthorizationCodeLifetime); - } + // Act + builder.SetUserinfoEndpointUris(Array.Empty()); - [Fact] - public void SetIdentityTokenLifetime_DefaultIdentityTokenLifetimeIsReplaced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42)); + // Assert + Assert.Empty(options.UserinfoEndpointUris); + } - var options = GetOptions(services); + [Fact] + public void SetUserinfoEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.IdentityTokenLifetime); - } + // Act + builder.SetUserinfoEndpointUris("http://localhost/endpoint-path"); - [Fact] - public void SetIdentityTokenLifetime_IdentityTokenLifetimeCanBeSetToNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.SetIdentityTokenLifetime(null); + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.UserinfoEndpointUris); + } - var options = GetOptions(services); + [Fact] + public void SetVerificationEndpointUris_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Null(options.IdentityTokenLifetime); - } + // Act and assert + var exception = Assert.Throws(() => builder.SetVerificationEndpointUris(addresses: (null as Uri[])!)); + Assert.Equal("addresses", exception.ParamName); + } - [Fact] - public void SetDeviceCodeLifetimeLifetime_DefaultDeviceCodeLifetimeIsReplaced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetVerificationEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetDeviceCodeLifetime(TimeSpan.FromMinutes(42)); + // Act and assert + var exception = Assert.Throws(() => builder.SetVerificationEndpointUris(addresses: (null as string[])!)); + Assert.Equal("addresses", exception.ParamName); + } - var options = GetOptions(services); + [Theory] + [InlineData(@"C:\")] + public void SetVerificationEndpointUris_ThrowsExceptionForMalformedUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetVerificationEndpointUris(new Uri(uri))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.GetResourceString(SR.ID0072), exception.Message); + } - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.DeviceCodeLifetime); - } + [Theory] + [InlineData("~/path")] + public void SetVerificationEndpointUris_ThrowsExceptionForInvalidRelativeUri(string uri) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetVerificationEndpointUris(new Uri(uri, UriKind.RelativeOrAbsolute))); + Assert.Equal("addresses", exception.ParamName); + Assert.Contains(SR.FormatID0081("~"), exception.Message); + } - [Fact] - public void SetDeviceCodeLifetimeLifetime_DeviceCodeLifetimeCanBeSetToNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetVerificationEndpointUris_ClearsUris() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetDeviceCodeLifetime(null); + // Act + builder.SetVerificationEndpointUris(Array.Empty()); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Null(options.DeviceCodeLifetime); - } + // Assert + Assert.Empty(options.VerificationEndpointUris); + } - [Fact] - public void SetUserCodeLifetime_DefaultUserCodeLifetimeIsReplaced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetVerificationEndpointUris_AddsUri() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetUserCodeLifetime(TimeSpan.FromMinutes(42)); + // Act + builder.SetVerificationEndpointUris("http://localhost/endpoint-path"); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.UserCodeLifetime); - } + // Assert + Assert.Contains(new Uri("http://localhost/endpoint-path"), options.VerificationEndpointUris); + } - [Fact] - public void SetUserCodeLifetime_UserLifetimeCanBeSetToNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void AcceptAnonymousClients_ClientIdentificationIsOptional() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetUserCodeLifetime(null); + // Act + builder.AcceptAnonymousClients(); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Null(options.UserCodeLifetime); - } + // Assert + Assert.True(options.AcceptAnonymousClients); + } - [Fact] - public void SetRefreshTokenLifetime_DefaultRefreshTokenLifetimeIsReplaced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetAccessTokenLifetime_DefaultAccessTokenLifetimeIsReplaced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42)); + // Act + builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(42)); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Equal(TimeSpan.FromMinutes(42), options.RefreshTokenLifetime); - } + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.AccessTokenLifetime); + } - [Fact] - public void SetRefreshTokenLifetime_RefreshTokenLifetimeCanBeSetToNull() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetAccessTokenLifetime_AccessTokenLifetimeCanBeSetToNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act - builder.SetRefreshTokenLifetime(null); + // Act + builder.SetAccessTokenLifetime(null); - var options = GetOptions(services); + var options = GetOptions(services); - // Assert - Assert.Null(options.RefreshTokenLifetime); - } + // Assert + Assert.Null(options.AccessTokenLifetime); + } - [Fact] - public void SetIssuer_ThrowsAnExceptionForNullIssuer() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + [Fact] + public void SetAuthorizationCodeLifetime_DefaultAuthorizationCodeLifetimeIsReplaced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Act and assert - var exception = Assert.Throws(() => builder.SetIssuer(null!)); + // Act + builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(42)); - Assert.Equal("address", exception.ParamName); - } + var options = GetOptions(services); - [Fact] - public void SetIssuer_AddressIsReplaced() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.AuthorizationCodeLifetime); + } - // Act - builder.SetIssuer(new Uri("http://www.fabrikam.com/")); + [Fact] + public void SetAuthorizationCodeLifetime_AuthorizationCodeLifetimeCanBeSetToNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - var options = GetOptions(services); + // Act + builder.SetAuthorizationCodeLifetime(null); - // Assert - Assert.Equal(new Uri("http://www.fabrikam.com/"), options.Issuer); - } + var options = GetOptions(services); - [Fact] - public void RegisterClaims_ClaimsAreAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Assert + Assert.Null(options.AuthorizationCodeLifetime); + } - // Act - builder.RegisterClaims("custom_claim_1", "custom_claim_2"); + [Fact] + public void SetIdentityTokenLifetime_DefaultIdentityTokenLifetimeIsReplaced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - var options = GetOptions(services); + // Act + builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(42)); - // Assert - Assert.Contains("custom_claim_1", options.Claims); - Assert.Contains("custom_claim_2", options.Claims); - } + var options = GetOptions(services); - [Fact] - public void RegisterClaims_ThrowsAnExceptionForNullClaims() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.RegisterClaims(claims: null!)); - Assert.Equal("claims", exception.ParamName); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void RegisterClaims_ThrowsAnExceptionForClaim(string claim) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - string[] claims = { claim }; + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.IdentityTokenLifetime); + } - // Act and assert - var exception = Assert.Throws(() => builder.RegisterClaims(claims)); + [Fact] + public void SetIdentityTokenLifetime_IdentityTokenLifetimeCanBeSetToNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - Assert.Equal("claims", exception.ParamName); - Assert.Contains("Claims cannot be null or empty.", exception.Message); - } + // Act + builder.SetIdentityTokenLifetime(null); - [Fact] - public void RegisterScopes_ScopesAreAdded() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + var options = GetOptions(services); - // Act - builder.RegisterScopes("custom_scope_1", "custom_scope_2"); + // Assert + Assert.Null(options.IdentityTokenLifetime); + } - var options = GetOptions(services); + [Fact] + public void SetDeviceCodeLifetimeLifetime_DefaultDeviceCodeLifetimeIsReplaced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - // Assert - Assert.Contains("custom_scope_1", options.Scopes); - Assert.Contains("custom_scope_2", options.Scopes); - } + // Act + builder.SetDeviceCodeLifetime(TimeSpan.FromMinutes(42)); - [Fact] - public void RegisterScopes_ThrowsAnExceptionForNullScopes() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.RegisterScopes(scopes: null!)); - Assert.Equal("scopes", exception.ParamName); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void RegisterScopes_ThrowsAnExceptionForScope(string scope) - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - string[] scopes = { scope }; + var options = GetOptions(services); - // Act and assert - var exception = Assert.Throws(() => builder.RegisterScopes(scopes)); + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.DeviceCodeLifetime); + } - Assert.Equal("scopes", exception.ParamName); - Assert.Contains("Scopes cannot be null or empty.", exception.Message); - } + [Fact] + public void SetDeviceCodeLifetimeLifetime_DeviceCodeLifetimeCanBeSetToNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void UseReferenceAccessTokens_ReferenceAccessTokensAreEnabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Act + builder.SetDeviceCodeLifetime(null); - // Act - builder.UseReferenceAccessTokens(); + var options = GetOptions(services); - var options = GetOptions(services); + // Assert + Assert.Null(options.DeviceCodeLifetime); + } - // Assert - Assert.True(options.UseReferenceAccessTokens); - } + [Fact] + public void SetUserCodeLifetime_DefaultUserCodeLifetimeIsReplaced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - [Fact] - public void UseReferenceRefreshTokens_ReferenceRefreshTokensAreEnabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); + // Act + builder.SetUserCodeLifetime(TimeSpan.FromMinutes(42)); - // Act - builder.UseReferenceRefreshTokens(); + var options = GetOptions(services); - var options = GetOptions(services); + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.UserCodeLifetime); + } - // Assert - Assert.True(options.UseReferenceRefreshTokens); - } + [Fact] + public void SetUserCodeLifetime_UserLifetimeCanBeSetToNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - private static IServiceCollection CreateServices() - { - return new ServiceCollection().AddOptions(); - } + // Act + builder.SetUserCodeLifetime(null); - private static OpenIddictServerBuilder CreateBuilder(IServiceCollection services) - => new OpenIddictServerBuilder(services); + var options = GetOptions(services); - private static OpenIddictServerOptions GetOptions(IServiceCollection services) - { - var provider = services.BuildServiceProvider(); + // Assert + Assert.Null(options.UserCodeLifetime); + } - var options = provider.GetRequiredService>(); - //return options.Get(OpenIddictServerDefaults.AuthenticationScheme); - return options.Value; - } + [Fact] + public void SetRefreshTokenLifetime_DefaultRefreshTokenLifetimeIsReplaced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); - private class CustomContext : BaseContext - { - /// - /// Creates a new instance of the class. - /// - public CustomContext(OpenIddictServerTransaction transaction) : base(transaction) { } - } + // Act + builder.SetRefreshTokenLifetime(TimeSpan.FromMinutes(42)); - private class CustomHandler : IOpenIddictServerHandler - { - public ValueTask HandleAsync(CustomContext context) => default; - } + var options = GetOptions(services); + + // Assert + Assert.Equal(TimeSpan.FromMinutes(42), options.RefreshTokenLifetime); + } + + [Fact] + public void SetRefreshTokenLifetime_RefreshTokenLifetimeCanBeSetToNull() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.SetRefreshTokenLifetime(null); + + var options = GetOptions(services); + + // Assert + Assert.Null(options.RefreshTokenLifetime); + } + + [Fact] + public void SetIssuer_ThrowsAnExceptionForNullIssuer() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.SetIssuer(null!)); + + Assert.Equal("address", exception.ParamName); + } + + [Fact] + public void SetIssuer_AddressIsReplaced() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.SetIssuer(new Uri("http://www.fabrikam.com/")); + + var options = GetOptions(services); + + // Assert + Assert.Equal(new Uri("http://www.fabrikam.com/"), options.Issuer); + } + + [Fact] + public void RegisterClaims_ClaimsAreAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.RegisterClaims("custom_claim_1", "custom_claim_2"); + + var options = GetOptions(services); + + // Assert + Assert.Contains("custom_claim_1", options.Claims); + Assert.Contains("custom_claim_2", options.Claims); + } + + [Fact] + public void RegisterClaims_ThrowsAnExceptionForNullClaims() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.RegisterClaims(claims: null!)); + Assert.Equal("claims", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void RegisterClaims_ThrowsAnExceptionForClaim(string claim) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + string[] claims = { claim }; + + // Act and assert + var exception = Assert.Throws(() => builder.RegisterClaims(claims)); + + Assert.Equal("claims", exception.ParamName); + Assert.Contains("Claims cannot be null or empty.", exception.Message); + } + + [Fact] + public void RegisterScopes_ScopesAreAdded() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.RegisterScopes("custom_scope_1", "custom_scope_2"); + + var options = GetOptions(services); + + // Assert + Assert.Contains("custom_scope_1", options.Scopes); + Assert.Contains("custom_scope_2", options.Scopes); + } + + [Fact] + public void RegisterScopes_ThrowsAnExceptionForNullScopes() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.RegisterScopes(scopes: null!)); + Assert.Equal("scopes", exception.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void RegisterScopes_ThrowsAnExceptionForScope(string scope) + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + string[] scopes = { scope }; + + // Act and assert + var exception = Assert.Throws(() => builder.RegisterScopes(scopes)); + + Assert.Equal("scopes", exception.ParamName); + Assert.Contains("Scopes cannot be null or empty.", exception.Message); + } + + [Fact] + public void UseReferenceAccessTokens_ReferenceAccessTokensAreEnabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.UseReferenceAccessTokens(); + + var options = GetOptions(services); + + // Assert + Assert.True(options.UseReferenceAccessTokens); + } + + [Fact] + public void UseReferenceRefreshTokens_ReferenceRefreshTokensAreEnabled() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act + builder.UseReferenceRefreshTokens(); + + var options = GetOptions(services); + + // Assert + Assert.True(options.UseReferenceRefreshTokens); + } + + private static IServiceCollection CreateServices() + { + return new ServiceCollection().AddOptions(); + } + + private static OpenIddictServerBuilder CreateBuilder(IServiceCollection services) + => new OpenIddictServerBuilder(services); + + private static OpenIddictServerOptions GetOptions(IServiceCollection services) + { + var provider = services.BuildServiceProvider(); + + var options = provider.GetRequiredService>(); + //return options.Get(OpenIddictServerDefaults.AuthenticationScheme); + return options.Value; + } + + private class CustomContext : BaseContext + { + /// + /// Creates a new instance of the class. + /// + public CustomContext(OpenIddictServerTransaction transaction) : base(transaction) { } + } + + private class CustomHandler : IOpenIddictServerHandler + { + public ValueTask HandleAsync(CustomContext context) => default; } -} \ No newline at end of file +} diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs index 7337a83d..0e72b04f 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs @@ -12,198 +12,197 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Xunit; -namespace OpenIddict.Server.Tests +namespace OpenIddict.Server.Tests; + +public class OpenIddictServerExtensionsTests { - public class OpenIddictServerExtensionsTests + [Fact] + public void AddServer_ThrowsAnExceptionForNullBuilder() + { + // Arrange + var builder = (OpenIddictBuilder) null!; + + // Act and assert + var exception = Assert.Throws(() => builder.AddServer()); + + Assert.Equal("builder", exception.ParamName); + } + + [Fact] + public void AddServer_ThrowsAnExceptionForNullConfiguration() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act and assert + var exception = Assert.Throws(() => builder.AddServer(configuration: null!)); + + Assert.Equal("configuration", exception.ParamName); + } + + [Fact] + public void AddServer_RegistersLoggingServices() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddServer(); + + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(ILogger<>)); + } + + [Fact] + public void AddServer_RegistersOptionsServices() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddServer(); + + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(IOptions<>)); + } + + [Fact] + public void AddServer_RegistersServerDispatcher() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddServer(); + + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictServerDispatcher) && + service.ImplementationType == typeof(OpenIddictServerDispatcher) && + service.Lifetime == ServiceLifetime.Scoped); + } + + [Fact] + public void AddServer_RegistersServerFactory() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddServer(); + + // Assert + Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictServerFactory) && + service.ImplementationType == typeof(OpenIddictServerFactory) && + service.Lifetime == ServiceLifetime.Scoped); + } + + public static IEnumerable DefaultHandlers + => OpenIddictServerHandlers.DefaultHandlers.Select(descriptor => new object[] { descriptor }); + + [Theory] + [MemberData(nameof(DefaultHandlers))] + public void AddServer_RegistersDefaultHandler(OpenIddictServerHandlerDescriptor descriptor) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddServer(); + + // Assert + Assert.Contains(services, service => service.Lifetime == descriptor.ServiceDescriptor.Lifetime && + service.ServiceType == descriptor.ServiceDescriptor.ServiceType && + service.ImplementationType == descriptor.ServiceDescriptor.ImplementationType); + } + + [Theory] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireAuthorizationStorageEnabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireAuthorizationRequest))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireClientIdParameter))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireConfigurationRequest))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireCryptographyRequest))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireDegradedModeDisabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireDeviceRequest))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireEndpointPermissionsEnabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireGrantTypePermissionsEnabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireIntrospectionRequest))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireLogoutRequest))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequirePostLogoutRedirectUriParameter))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireReferenceAccessTokensEnabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireReferenceRefreshTokensEnabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireRevocationRequest))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireSlidingRefreshTokenExpirationEnabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireScopePermissionsEnabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireScopeValidationEnabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireTokenStorageEnabled))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireTokenRequest))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireUserinfoRequest))] + [InlineData(typeof(OpenIddictServerHandlerFilters.RequireVerificationRequest))] + public void AddServer_RegistersRequiredSingletons(Type type) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddServer(); + + // Assert + Assert.Contains(services, service => service.ServiceType == type && + service.ImplementationType == type && + service.Lifetime == ServiceLifetime.Singleton); + } + + [Fact] + public void AddServer_ResolvingProviderThrowsAnExceptionWhenCoreServicesAreNotRegistered() + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddServer(); + + // Assert + var provider = services.BuildServiceProvider(); + + var exception = Assert.Throws(() => provider.GetRequiredService()); + + Assert.NotNull(exception); + } + + [Theory] + [InlineData(typeof(IPostConfigureOptions), typeof(OpenIddictServerConfiguration))] + public void AddServer_RegistersConfiguration(Type serviceType, Type implementationType) + { + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act + builder.AddServer(); + + // Assert + Assert.Contains(services, service => service.ServiceType == serviceType && + service.ImplementationType == implementationType); + } + + [Fact] + public void AddServer_CanBeSafelyInvokedMultipleTimes() { - [Fact] - public void AddServer_ThrowsAnExceptionForNullBuilder() - { - // Arrange - var builder = (OpenIddictBuilder) null!; - - // Act and assert - var exception = Assert.Throws(() => builder.AddServer()); - - Assert.Equal("builder", exception.ParamName); - } - - [Fact] - public void AddServer_ThrowsAnExceptionForNullConfiguration() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act and assert - var exception = Assert.Throws(() => builder.AddServer(configuration: null!)); - - Assert.Equal("configuration", exception.ParamName); - } - - [Fact] - public void AddServer_RegistersLoggingServices() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddServer(); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(ILogger<>)); - } - - [Fact] - public void AddServer_RegistersOptionsServices() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddServer(); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(IOptions<>)); - } - - [Fact] - public void AddServer_RegistersServerDispatcher() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddServer(); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictServerDispatcher) && - service.ImplementationType == typeof(OpenIddictServerDispatcher) && - service.Lifetime == ServiceLifetime.Scoped); - } - - [Fact] - public void AddServer_RegistersServerFactory() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddServer(); - - // Assert - Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictServerFactory) && - service.ImplementationType == typeof(OpenIddictServerFactory) && - service.Lifetime == ServiceLifetime.Scoped); - } - - public static IEnumerable DefaultHandlers - => OpenIddictServerHandlers.DefaultHandlers.Select(descriptor => new object[] { descriptor }); - - [Theory] - [MemberData(nameof(DefaultHandlers))] - public void AddServer_RegistersDefaultHandler(OpenIddictServerHandlerDescriptor descriptor) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddServer(); - - // Assert - Assert.Contains(services, service => service.Lifetime == descriptor.ServiceDescriptor.Lifetime && - service.ServiceType == descriptor.ServiceDescriptor.ServiceType && - service.ImplementationType == descriptor.ServiceDescriptor.ImplementationType); - } - - [Theory] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireAuthorizationStorageEnabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireAuthorizationRequest))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireClientIdParameter))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireConfigurationRequest))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireCryptographyRequest))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireDegradedModeDisabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireDeviceRequest))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireEndpointPermissionsEnabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireGrantTypePermissionsEnabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireIntrospectionRequest))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireLogoutRequest))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequirePostLogoutRedirectUriParameter))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireReferenceAccessTokensEnabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireReferenceRefreshTokensEnabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireRevocationRequest))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireSlidingRefreshTokenExpirationEnabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireScopePermissionsEnabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireScopeValidationEnabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireTokenStorageEnabled))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireTokenRequest))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireUserinfoRequest))] - [InlineData(typeof(OpenIddictServerHandlerFilters.RequireVerificationRequest))] - public void AddServer_RegistersRequiredSingletons(Type type) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddServer(); - - // Assert - Assert.Contains(services, service => service.ServiceType == type && - service.ImplementationType == type && - service.Lifetime == ServiceLifetime.Singleton); - } - - [Fact] - public void AddServer_ResolvingProviderThrowsAnExceptionWhenCoreServicesAreNotRegistered() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddServer(); - - // Assert - var provider = services.BuildServiceProvider(); - - var exception = Assert.Throws(() => provider.GetRequiredService()); - - Assert.NotNull(exception); - } - - [Theory] - [InlineData(typeof(IPostConfigureOptions), typeof(OpenIddictServerConfiguration))] - public void AddServer_RegistersConfiguration(Type serviceType, Type implementationType) - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act - builder.AddServer(); - - // Assert - Assert.Contains(services, service => service.ServiceType == serviceType && - service.ImplementationType == implementationType); - } - - [Fact] - public void AddServer_CanBeSafelyInvokedMultipleTimes() - { - // Arrange - var services = new ServiceCollection(); - var builder = new OpenIddictBuilder(services); - - // Act and assert - builder.AddServer(); - builder.AddServer(); - builder.AddServer(); - } + // Arrange + var services = new ServiceCollection(); + var builder = new OpenIddictBuilder(services); + + // Act and assert + builder.AddServer(); + builder.AddServer(); + builder.AddServer(); } -} \ No newline at end of file +}