From 9a0689150765df61c4ad2d5f22b8c79c8e63cd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Thu, 6 Sep 2018 18:41:35 +0200 Subject: [PATCH] Tweak the events model to force user-defined handlers to explicitly determine whether other handlers can be invoked --- OpenIddict.sln | 11 +- .../Helpers/OpenIddictHelpers.cs | 25 ++- .../OpenIddict.Extensions.csproj | 10 ++ src/OpenIddict.Core/OpenIddict.Core.csproj | 4 + src/OpenIddict.Core/OpenIddictCoreBuilder.cs | 17 +- .../OpenIddict.EntityFramework.csproj | 6 +- .../OpenIddictApplicationStoreResolver.cs | 4 +- .../OpenIddictAuthorizationStoreResolver.cs | 4 +- .../Resolvers/OpenIddictScopeStoreResolver.cs | 4 +- .../Resolvers/OpenIddictTokenStoreResolver.cs | 4 +- .../OpenIddict.EntityFrameworkCore.csproj | 4 + .../OpenIddictApplicationStoreResolver.cs | 4 +- .../OpenIddictAuthorizationStoreResolver.cs | 5 +- .../Resolvers/OpenIddictScopeStoreResolver.cs | 4 +- .../Resolvers/OpenIddictTokenStoreResolver.cs | 4 +- .../IOpenIddictServerEventHandler.cs | 9 +- .../IOpenIddictServerEventService.cs | 26 +++ .../Internal/OpenIddictServerEventService.cs | 163 ------------------ .../Internal/OpenIddictServerProvider.cs | 5 +- .../OpenIddict.Server.csproj | 4 + .../OpenIddictServerBuilder.cs | 67 +++---- .../OpenIddictServerEventHandler.cs | 29 +--- .../OpenIddictServerEventService.cs | 52 ++++++ .../OpenIddictServerEventState.cs | 33 ++++ .../OpenIddictServerExtensions.cs | 4 +- .../IOpenIddictValidationEventHandler.cs | 9 +- .../IOpenIddictValidationEventService.cs | 25 +++ .../OpenIddictValidationEventService.cs | 76 -------- .../Internal/OpenIddictValidationProvider.cs | 4 +- .../OpenIddict.Validation.csproj | 4 + .../OpenIddictValidationBuilder.cs | 68 +++----- .../OpenIddictValidationEventHandler.cs | 29 +--- .../OpenIddictValidationEventService.cs | 52 ++++++ .../OpenIddictValidationEventState.cs | 33 ++++ .../OpenIddictValidationExtensions.cs | 2 +- .../OpenIddictServerBuilderTests.cs | 64 +++++-- .../OpenIddictServerEventHandlerTests.cs | 60 +++++++ .../OpenIddictServerEventServiceTests.cs | 92 ++++++++++ .../OpenIddictServerExtensionsTests.cs | 2 +- .../OpenIddictValidationBuilderTests.cs | 64 +++++-- .../OpenIddictValidationEventHandlerTests.cs | 60 +++++++ .../OpenIddictValidationEventServiceTests.cs | 92 ++++++++++ .../OpenIddictValidationExtensionsTests.cs | 2 +- 43 files changed, 788 insertions(+), 452 deletions(-) rename src/OpenIddict.Core/OpenIddictCoreHelpers.cs => shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs (68%) create mode 100644 shared/OpenIddict.Extensions/OpenIddict.Extensions.csproj create mode 100644 src/OpenIddict.Server/IOpenIddictServerEventService.cs delete mode 100644 src/OpenIddict.Server/Internal/OpenIddictServerEventService.cs create mode 100644 src/OpenIddict.Server/OpenIddictServerEventService.cs create mode 100644 src/OpenIddict.Server/OpenIddictServerEventState.cs create mode 100644 src/OpenIddict.Validation/IOpenIddictValidationEventService.cs delete mode 100644 src/OpenIddict.Validation/Internal/OpenIddictValidationEventService.cs create mode 100644 src/OpenIddict.Validation/OpenIddictValidationEventService.cs create mode 100644 src/OpenIddict.Validation/OpenIddictValidationEventState.cs create mode 100644 test/OpenIddict.Server.Tests/OpenIddictServerEventHandlerTests.cs create mode 100644 test/OpenIddict.Server.Tests/OpenIddictServerEventServiceTests.cs create mode 100644 test/OpenIddict.Validation.Tests/OpenIddictValidationEventHandlerTests.cs create mode 100644 test/OpenIddict.Validation.Tests/OpenIddictValidationEventServiceTests.cs diff --git a/OpenIddict.sln b/OpenIddict.sln index bfaef2b9..5a8a75af 100644 --- a/OpenIddict.sln +++ b/OpenIddict.sln @@ -61,7 +61,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.MongoDb.Models", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Abstractions.Tests", "test\OpenIddict.Abstractions.Tests\OpenIddict.Abstractions.Tests.csproj", "{8FACE85E-EF8F-4AB1-85DD-4010D5E2165D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.MongoDb.Tests", "test\OpenIddict.MongoDb.Tests\OpenIddict.MongoDb.Tests.csproj", "{27F603EF-D335-445B-9443-6B5A6CA3C110}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.MongoDb.Tests", "test\OpenIddict.MongoDb.Tests\OpenIddict.MongoDb.Tests.csproj", "{27F603EF-D335-445B-9443-6B5A6CA3C110}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{D8075F1F-6257-463B-B481-BDC7C5ABA292}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.Extensions", "shared\OpenIddict.Extensions\OpenIddict.Extensions.csproj", "{B90761B9-7582-44CB-AB0D-3C4058693227}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -157,6 +161,10 @@ Global {27F603EF-D335-445B-9443-6B5A6CA3C110}.Debug|Any CPU.Build.0 = Debug|Any CPU {27F603EF-D335-445B-9443-6B5A6CA3C110}.Release|Any CPU.ActiveCfg = Release|Any CPU {27F603EF-D335-445B-9443-6B5A6CA3C110}.Release|Any CPU.Build.0 = Release|Any CPU + {B90761B9-7582-44CB-AB0D-3C4058693227}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B90761B9-7582-44CB-AB0D-3C4058693227}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B90761B9-7582-44CB-AB0D-3C4058693227}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B90761B9-7582-44CB-AB0D-3C4058693227}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -184,6 +192,7 @@ Global {14C55FB6-9626-4BDE-8961-3BE91DDD6418} = {D544447C-D701-46BB-9A5B-C76C612A596B} {8FACE85E-EF8F-4AB1-85DD-4010D5E2165D} = {5FC71D6A-A994-4F62-977F-88A7D25379D7} {27F603EF-D335-445B-9443-6B5A6CA3C110} = {5FC71D6A-A994-4F62-977F-88A7D25379D7} + {B90761B9-7582-44CB-AB0D-3C4058693227} = {D8075F1F-6257-463B-B481-BDC7C5ABA292} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A710059F-0466-4D48-9B3A-0EF4F840B616} diff --git a/src/OpenIddict.Core/OpenIddictCoreHelpers.cs b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs similarity index 68% rename from src/OpenIddict.Core/OpenIddictCoreHelpers.cs rename to shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs index ee5f43aa..4383668d 100644 --- a/src/OpenIddict.Core/OpenIddictCoreHelpers.cs +++ b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs @@ -1,21 +1,30 @@ using System; -using System.ComponentModel; +using System.Collections.Generic; +using System.Linq; -namespace OpenIddict.Core +namespace OpenIddict.Extensions { /// /// Exposes common helpers used by the OpenIddict assemblies. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static class OpenIddictCoreHelpers + internal static class OpenIddictHelpers { /// - /// Finds the base type that matches the specified generic type definition. + /// 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(); + + /// + /// 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 == null) { @@ -43,7 +52,7 @@ namespace OpenIddict.Core if (contract.GetGenericTypeDefinition() == definition) { - return contract; + yield return contract; } } } @@ -59,12 +68,10 @@ namespace OpenIddict.Core if (candidate.GetGenericTypeDefinition() == definition) { - return candidate; + yield return candidate; } } } - - return null; } } } diff --git a/shared/OpenIddict.Extensions/OpenIddict.Extensions.csproj b/shared/OpenIddict.Extensions/OpenIddict.Extensions.csproj new file mode 100644 index 00000000..0f2e59bf --- /dev/null +++ b/shared/OpenIddict.Extensions/OpenIddict.Extensions.csproj @@ -0,0 +1,10 @@ + + + + + + netstandard2.0 + false + + + diff --git a/src/OpenIddict.Core/OpenIddict.Core.csproj b/src/OpenIddict.Core/OpenIddict.Core.csproj index cc6d4cc3..45704cf9 100644 --- a/src/OpenIddict.Core/OpenIddict.Core.csproj +++ b/src/OpenIddict.Core/OpenIddict.Core.csproj @@ -23,4 +23,8 @@ + + + + diff --git a/src/OpenIddict.Core/OpenIddictCoreBuilder.cs b/src/OpenIddict.Core/OpenIddictCoreBuilder.cs index 25112337..44aeca6d 100644 --- a/src/OpenIddict.Core/OpenIddictCoreBuilder.cs +++ b/src/OpenIddict.Core/OpenIddictCoreBuilder.cs @@ -10,6 +10,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection.Extensions; using OpenIddict.Abstractions; using OpenIddict.Core; +using OpenIddict.Extensions; namespace Microsoft.Extensions.DependencyInjection { @@ -86,7 +87,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(type)); } - var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(IOpenIddictApplicationStore<>)); + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictApplicationStore<>)); if (root == null) { throw new ArgumentException("The specified type is invalid.", nameof(type)); @@ -143,7 +144,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(type)); } - var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(IOpenIddictAuthorizationStore<>)); + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictAuthorizationStore<>)); if (root == null) { throw new ArgumentException("The specified type is invalid.", nameof(type)); @@ -200,7 +201,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(type)); } - var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(IOpenIddictScopeStore<>)); + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictScopeStore<>)); if (root == null) { throw new ArgumentException("The specified type is invalid.", nameof(type)); @@ -257,7 +258,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(type)); } - var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(IOpenIddictTokenStore<>)); + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictTokenStore<>)); if (root == null) { throw new ArgumentException("The specified type is invalid.", nameof(type)); @@ -314,7 +315,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(type)); } - var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(OpenIddictApplicationManager<>)); + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictApplicationManager<>)); if (root == null) { throw new ArgumentException("The specified type is invalid.", nameof(type)); @@ -411,7 +412,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(type)); } - var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(OpenIddictAuthorizationManager<>)); + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictAuthorizationManager<>)); if (root == null) { throw new ArgumentException("The specified type is invalid.", nameof(type)); @@ -507,7 +508,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(type)); } - var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(OpenIddictScopeManager<>)); + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictScopeManager<>)); if (root == null) { throw new ArgumentException("The specified type is invalid.", nameof(type)); @@ -604,7 +605,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(type)); } - var root = OpenIddictCoreHelpers.FindGenericBaseType(type, typeof(OpenIddictTokenManager<>)); + var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictTokenManager<>)); if (root == null) { throw new ArgumentException("The specified type is invalid.", nameof(type)); diff --git a/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj index 37e883cb..fcfe5273 100644 --- a/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj +++ b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj @@ -1,4 +1,4 @@ - + @@ -23,4 +23,8 @@ + + + + diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs index 73a0e076..044cfa11 100644 --- a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs @@ -11,8 +11,8 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; -using OpenIddict.Core; using OpenIddict.EntityFramework.Models; +using OpenIddict.Extensions; namespace OpenIddict.EntityFramework { @@ -49,7 +49,7 @@ namespace OpenIddict.EntityFramework var type = _cache.GetOrAdd(typeof(TApplication), key => { - var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictApplication<,,>)); + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictApplication<,,>)); if (root == null) { throw new InvalidOperationException(new StringBuilder() diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs index 79219a0d..9683733d 100644 --- a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs @@ -11,8 +11,8 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; -using OpenIddict.Core; using OpenIddict.EntityFramework.Models; +using OpenIddict.Extensions; namespace OpenIddict.EntityFramework { @@ -49,7 +49,7 @@ namespace OpenIddict.EntityFramework var type = _cache.GetOrAdd(typeof(TAuthorization), key => { - var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictAuthorization<,,>)); + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictAuthorization<,,>)); if (root == null) { throw new InvalidOperationException(new StringBuilder() diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs index e435d37a..cfe05634 100644 --- a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs @@ -11,8 +11,8 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; -using OpenIddict.Core; using OpenIddict.EntityFramework.Models; +using OpenIddict.Extensions; namespace OpenIddict.EntityFramework { @@ -49,7 +49,7 @@ namespace OpenIddict.EntityFramework var type = _cache.GetOrAdd(typeof(TScope), key => { - var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictScope<>)); + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictScope<>)); if (root == null) { throw new InvalidOperationException(new StringBuilder() diff --git a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs index 7d020653..eebd7503 100644 --- a/src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs +++ b/src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs @@ -11,8 +11,8 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; -using OpenIddict.Core; using OpenIddict.EntityFramework.Models; +using OpenIddict.Extensions; namespace OpenIddict.EntityFramework { @@ -49,7 +49,7 @@ namespace OpenIddict.EntityFramework var type = _cache.GetOrAdd(typeof(TToken), key => { - var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictToken<,,>)); + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictToken<,,>)); if (root == null) { throw new InvalidOperationException(new StringBuilder() diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj b/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj index 8c8a1cdc..1a45afbd 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj @@ -23,4 +23,8 @@ + + + + diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs index 1c650014..030a0f2c 100644 --- a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs @@ -11,8 +11,8 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; -using OpenIddict.Core; using OpenIddict.EntityFrameworkCore.Models; +using OpenIddict.Extensions; namespace OpenIddict.EntityFrameworkCore { @@ -49,7 +49,7 @@ namespace OpenIddict.EntityFrameworkCore var type = _cache.GetOrAdd(typeof(TApplication), key => { - var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictApplication<,,>)); + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictApplication<,,>)); if (root == null) { throw new InvalidOperationException(new StringBuilder() diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs index 3d6c1fc8..8964735e 100644 --- a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs @@ -8,12 +8,11 @@ using System; using System.Collections.Concurrent; using System.Text; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; -using OpenIddict.Core; using OpenIddict.EntityFrameworkCore.Models; +using OpenIddict.Extensions; namespace OpenIddict.EntityFrameworkCore { @@ -50,7 +49,7 @@ namespace OpenIddict.EntityFrameworkCore var type = _cache.GetOrAdd(typeof(TAuthorization), key => { - var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictAuthorization<,,>)); + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictAuthorization<,,>)); if (root == null) { throw new InvalidOperationException(new StringBuilder() diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs index ef419e8a..9c249b78 100644 --- a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs @@ -11,8 +11,8 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; -using OpenIddict.Core; using OpenIddict.EntityFrameworkCore.Models; +using OpenIddict.Extensions; namespace OpenIddict.EntityFrameworkCore { @@ -49,7 +49,7 @@ namespace OpenIddict.EntityFrameworkCore var type = _cache.GetOrAdd(typeof(TScope), key => { - var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictScope<>)); + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictScope<>)); if (root == null) { throw new InvalidOperationException(new StringBuilder() diff --git a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs index e009bee8..860ca846 100644 --- a/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs +++ b/src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs @@ -11,8 +11,8 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; -using OpenIddict.Core; using OpenIddict.EntityFrameworkCore.Models; +using OpenIddict.Extensions; namespace OpenIddict.EntityFrameworkCore { @@ -49,7 +49,7 @@ namespace OpenIddict.EntityFrameworkCore var type = _cache.GetOrAdd(typeof(TToken), key => { - var root = OpenIddictCoreHelpers.FindGenericBaseType(key, typeof(OpenIddictToken<,,>)); + var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictToken<,,>)); if (root == null) { throw new InvalidOperationException(new StringBuilder() diff --git a/src/OpenIddict.Server/IOpenIddictServerEventHandler.cs b/src/OpenIddict.Server/IOpenIddictServerEventHandler.cs index 0509a5be..b2746180 100644 --- a/src/OpenIddict.Server/IOpenIddictServerEventHandler.cs +++ b/src/OpenIddict.Server/IOpenIddictServerEventHandler.cs @@ -4,7 +4,6 @@ * the license and the contributors participating to this project. */ -using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -20,12 +19,10 @@ namespace OpenIddict.Server /// Processes the event. /// /// The event to process. - /// - /// 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 determines whether next handlers in the pipeline are invoked. /// - Task HandleAsync([NotNull] TEvent notification, CancellationToken cancellationToken); + Task HandleAsync([NotNull] TEvent notification); } } diff --git a/src/OpenIddict.Server/IOpenIddictServerEventService.cs b/src/OpenIddict.Server/IOpenIddictServerEventService.cs new file mode 100644 index 00000000..81a4944d --- /dev/null +++ b/src/OpenIddict.Server/IOpenIddictServerEventService.cs @@ -0,0 +1,26 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace OpenIddict.Server +{ + /// + /// Dispatches events by invoking the corresponding handlers. + /// + public interface IOpenIddictServerEventService + { + /// + /// Publishes a new event. + /// + /// The type of the event to publish. + /// The event to publish. + /// A that can be used to monitor the asynchronous operation. + Task PublishAsync([NotNull] TEvent notification) where TEvent : class, IOpenIddictServerEvent; + } +} diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerEventService.cs b/src/OpenIddict.Server/Internal/OpenIddictServerEventService.cs deleted file mode 100644 index bb160eee..00000000 --- a/src/OpenIddict.Server/Internal/OpenIddictServerEventService.cs +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System; -using System.Threading; -using System.Threading.Tasks; -using AspNet.Security.OpenIdConnect.Primitives; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; -using static OpenIddict.Server.OpenIddictServerEvents; - -namespace OpenIddict.Server.Internal -{ - /// - /// Dispatches events by invoking the corresponding notification handlers. - /// Note: this API supports the OpenIddict infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future minor releases. - /// - public class OpenIddictServerEventService - { - private readonly IServiceProvider _provider; - - /// - /// Creates a new instance of the class. - /// Note: this API supports the OpenIddict infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future minor releases. - /// - public OpenIddictServerEventService([NotNull] IServiceProvider provider) - { - _provider = provider; - } - - /// - /// Publishes a new event. - /// - /// The type of the event to publish. - /// The event to publish. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - public async Task PublishAsync([NotNull] TEvent notification, CancellationToken cancellationToken = default) - where TEvent : class, IOpenIddictServerEvent - { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } - - foreach (var handler in _provider.GetServices>()) - { - cancellationToken.ThrowIfCancellationRequested(); - - await handler.HandleAsync(notification, cancellationToken); - - // Note: the following logic determines whether next handlers should be invoked - // depending on whether the underlying event context was substantially updated. - switch (notification) - { - case MatchEndpoint value when value.Context.Result != null: return; - case MatchEndpoint value when value.Context.IsAuthorizationEndpoint || - value.Context.IsConfigurationEndpoint || - value.Context.IsCryptographyEndpoint || - value.Context.IsIntrospectionEndpoint || - value.Context.IsLogoutEndpoint || - value.Context.IsRevocationEndpoint || - value.Context.IsTokenEndpoint || - value.Context.IsUserinfoEndpoint: return; - - case ExtractAuthorizationRequest value when value.Context.Result != null: return; - case ExtractConfigurationRequest value when value.Context.Result != null: return; - case ExtractCryptographyRequest value when value.Context.Result != null: return; - case ExtractIntrospectionRequest value when value.Context.Result != null: return; - case ExtractLogoutRequest value when value.Context.Result != null: return; - case ExtractRevocationRequest value when value.Context.Result != null: return; - case ExtractTokenRequest value when value.Context.Result != null: return; - case ExtractUserinfoRequest value when value.Context.Result != null: return; - - case ValidateAuthorizationRequest value when value.Context.Result != null: return; - case ValidateConfigurationRequest value when value.Context.Result != null: return; - case ValidateCryptographyRequest value when value.Context.Result != null: return; - case ValidateIntrospectionRequest value when value.Context.Result != null: return; - case ValidateLogoutRequest value when value.Context.Result != null: return; - case ValidateRevocationRequest value when value.Context.Result != null: return; - case ValidateTokenRequest value when value.Context.Result != null: return; - case ValidateUserinfoRequest value when value.Context.Result != null: return; - - case ValidateAuthorizationRequest value when value.Context.IsRejected: return; - case ValidateConfigurationRequest value when value.Context.IsRejected: return; - case ValidateCryptographyRequest value when value.Context.IsRejected: return; - case ValidateIntrospectionRequest value when value.Context.IsRejected: return; - case ValidateLogoutRequest value when value.Context.IsRejected: return; - case ValidateRevocationRequest value when value.Context.IsRejected: return; - case ValidateTokenRequest value when value.Context.IsRejected: return; - case ValidateUserinfoRequest value when value.Context.IsRejected: return; - - case ValidateIntrospectionRequest value when value.Context.IsSkipped: return; - case ValidateRevocationRequest value when value.Context.IsSkipped: return; - case ValidateTokenRequest value when value.Context.IsSkipped: return; - - case HandleAuthorizationRequest value when value.Context.Result != null: return; - case HandleConfigurationRequest value when value.Context.Result != null: return; - case HandleCryptographyRequest value when value.Context.Result != null: return; - case HandleIntrospectionRequest value when value.Context.Result != null: return; - case HandleLogoutRequest value when value.Context.Result != null: return; - case HandleRevocationRequest value when value.Context.Result != null: return; - case HandleTokenRequest value when value.Context.Result != null: return; - case HandleUserinfoRequest value when value.Context.Result != null: return; - - case HandleAuthorizationRequest value when value.Context.Ticket != null: return; - - case HandleTokenRequest value when value.Context.Ticket != null && - !value.Context.Request.IsAuthorizationCodeGrantType() && - !value.Context.Request.IsRefreshTokenGrantType(): return; - - case HandleTokenRequest value when value.Context.Ticket == null && - (value.Context.Request.IsAuthorizationCodeGrantType() || - value.Context.Request.IsRefreshTokenGrantType()): return; - - case HandleAuthorizationRequest value when value.Context.Ticket != null: return; - - case ProcessChallengeResponse value when value.Context.Result != null: return; - case ProcessSigninResponse value when value.Context.Result != null: return; - case ProcessSignoutResponse value when value.Context.Result != null: return; - - case ProcessChallengeResponse value when value.Context.IsRejected: return; - case ProcessSigninResponse value when value.Context.IsRejected: return; - case ProcessSignoutResponse value when value.Context.IsRejected: return; - - case ApplyAuthorizationResponse value when value.Context.Result != null: return; - case ApplyConfigurationResponse value when value.Context.Result != null: return; - case ApplyCryptographyResponse value when value.Context.Result != null: return; - case ApplyIntrospectionResponse value when value.Context.Result != null: return; - case ApplyLogoutResponse value when value.Context.Result != null: return; - case ApplyRevocationResponse value when value.Context.Result != null: return; - case ApplyTokenResponse value when value.Context.Result != null: return; - case ApplyUserinfoResponse value when value.Context.Result != null: return; - - case DeserializeAuthorizationCode value when value.Context.IsHandled: return; - case DeserializeAccessToken value when value.Context.IsHandled: return; - case DeserializeIdentityToken value when value.Context.IsHandled: return; - case DeserializeRefreshToken value when value.Context.IsHandled: return; - - case DeserializeAuthorizationCode value when value.Context.Ticket != null: return; - case DeserializeAccessToken value when value.Context.Ticket != null: return; - case DeserializeIdentityToken value when value.Context.Ticket != null: return; - case DeserializeRefreshToken value when value.Context.Ticket != null: return; - - case SerializeAuthorizationCode value when value.Context.IsHandled: return; - case SerializeAccessToken value when value.Context.IsHandled: return; - case SerializeIdentityToken value when value.Context.IsHandled: return; - case SerializeRefreshToken value when value.Context.IsHandled: return; - - case SerializeAuthorizationCode value when !string.IsNullOrEmpty(value.Context.AuthorizationCode): return; - case SerializeAccessToken value when !string.IsNullOrEmpty(value.Context.AccessToken): return; - case SerializeIdentityToken value when !string.IsNullOrEmpty(value.Context.IdentityToken): return; - case SerializeRefreshToken value when !string.IsNullOrEmpty(value.Context.RefreshToken): return; - } - } - } - } -} diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs index 3570deef..bbc32d32 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs @@ -5,7 +5,6 @@ */ using System; -using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Text; @@ -28,7 +27,7 @@ namespace OpenIddict.Server.Internal public sealed partial class OpenIddictServerProvider : OpenIdConnectServerProvider { private readonly ILogger _logger; - private readonly OpenIddictServerEventService _eventService; + private readonly IOpenIddictServerEventService _eventService; private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictAuthorizationManager _authorizationManager; private readonly IOpenIddictScopeManager _scopeManager; @@ -41,7 +40,7 @@ namespace OpenIddict.Server.Internal /// public OpenIddictServerProvider( [NotNull] ILogger logger, - [NotNull] OpenIddictServerEventService eventService, + [NotNull] IOpenIddictServerEventService eventService, [NotNull] IOpenIddictApplicationManager applicationManager, [NotNull] IOpenIddictAuthorizationManager authorizationManager, [NotNull] IOpenIddictScopeManager scopeManager, diff --git a/src/OpenIddict.Server/OpenIddict.Server.csproj b/src/OpenIddict.Server/OpenIddict.Server.csproj index 4decf8eb..3335441b 100644 --- a/src/OpenIddict.Server/OpenIddict.Server.csproj +++ b/src/OpenIddict.Server/OpenIddict.Server.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs index 316f03cd..e49a735a 100644 --- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs +++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs @@ -12,7 +12,6 @@ using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography.X509Certificates; -using System.Threading; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Primitives; using JetBrains.Annotations; @@ -20,6 +19,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; using Microsoft.IdentityModel.Tokens; +using OpenIddict.Extensions; using OpenIddict.Server; namespace Microsoft.Extensions.DependencyInjection @@ -50,13 +50,13 @@ namespace Microsoft.Extensions.DependencyInjection public IServiceCollection Services { get; } /// - /// Registers an event handler for the specified event type. + /// Registers an inline event handler for the specified event type. /// - /// The handler added to the DI container. + /// The handler delegate. /// The . [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictServerBuilder AddEventHandler( - [NotNull] IOpenIddictServerEventHandler handler) + [NotNull] Func> handler) where TEvent : class, IOpenIddictServerEvent { if (handler == null) @@ -64,62 +64,30 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(handler)); } - Services.AddSingleton(handler); + Services.AddSingleton>( + new OpenIddictServerEventHandler(handler)); return this; } /// - /// Registers an event handler for the specified event type. + /// Registers an event handler that will be invoked for all the events listed by the implemented interfaces. /// - /// The handler added to the DI container. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerBuilder AddEventHandler([NotNull] Func handler) - where TEvent : class, IOpenIddictServerEvent - => AddEventHandler((notification, cancellationToken) => handler(notification)); - - /// - /// Registers an event handler for the specified event type. - /// - /// The handler added to the DI container. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerBuilder AddEventHandler([NotNull] Func handler) - where TEvent : class, IOpenIddictServerEvent - { - if (handler == null) - { - throw new ArgumentNullException(nameof(handler)); - } - - return AddEventHandler(new OpenIddictServerEventHandler(handler)); - } - - /// - /// Registers an event handler for the specified event type. - /// - /// The type of the event. /// The type of the handler. /// The lifetime of the registered service. /// The . [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerBuilder AddEventHandler( - ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TEvent : class, IOpenIddictServerEvent - where THandler : IOpenIddictServerEventHandler - => AddEventHandler(typeof(THandler)); + public OpenIddictServerBuilder AddEventHandler(ServiceLifetime lifetime = ServiceLifetime.Scoped) + => AddEventHandler(typeof(THandler), lifetime); /// - /// Registers an event handler for the specified event type. + /// Registers an event handler that will be invoked for all the events listed by the implemented interfaces. /// /// The type of the handler. /// The lifetime of the registered service. /// The . [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictServerBuilder AddEventHandler( - [NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TEvent : class, IOpenIddictServerEvent + public OpenIddictServerBuilder AddEventHandler([NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) { if (type == null) { @@ -131,12 +99,21 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentException("Handlers cannot be registered as transient services.", nameof(lifetime)); } - if (!typeof(IOpenIddictServerEventHandler).IsAssignableFrom(type)) + if (type.IsGenericTypeDefinition) { throw new ArgumentException("The specified type is invalid.", nameof(type)); } - Services.Add(new ServiceDescriptor(typeof(IOpenIddictServerEventHandler), type, lifetime)); + var services = OpenIddictHelpers.FindGenericBaseTypes(type, typeof(IOpenIddictServerEventHandler<>)).ToArray(); + if (services.Length == 0) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + foreach (var service in services) + { + Services.Add(new ServiceDescriptor(service, type, lifetime)); + } return this; } diff --git a/src/OpenIddict.Server/OpenIddictServerEventHandler.cs b/src/OpenIddict.Server/OpenIddictServerEventHandler.cs index bc9dd991..b0d9e490 100644 --- a/src/OpenIddict.Server/OpenIddictServerEventHandler.cs +++ b/src/OpenIddict.Server/OpenIddictServerEventHandler.cs @@ -5,7 +5,6 @@ */ using System; -using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -18,38 +17,24 @@ namespace OpenIddict.Server public class OpenIddictServerEventHandler : IOpenIddictServerEventHandler where TEvent : class, IOpenIddictServerEvent { - private readonly Func _handler; + private readonly Func> _handler; /// /// Creates a new event using the specified handler delegate. /// - /// The event handler delegate - public OpenIddictServerEventHandler([NotNull] Func handler) + /// The event handler delegate. + public OpenIddictServerEventHandler([NotNull] Func> handler) => _handler = handler ?? throw new ArgumentNullException(nameof(handler)); /// /// Processes the event. /// /// The event to process. - /// - /// 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 determines whether next handlers in the pipeline are invoked. /// - public Task HandleAsync(TEvent notification, CancellationToken cancellationToken) - { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } - - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - return _handler.Invoke(notification, cancellationToken); - } + public Task HandleAsync(TEvent notification) + => _handler(notification ?? throw new ArgumentNullException(nameof(notification))); } } diff --git a/src/OpenIddict.Server/OpenIddictServerEventService.cs b/src/OpenIddict.Server/OpenIddictServerEventService.cs new file mode 100644 index 00000000..f53ec8cf --- /dev/null +++ b/src/OpenIddict.Server/OpenIddictServerEventService.cs @@ -0,0 +1,52 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace OpenIddict.Server +{ + /// + /// Dispatches events by invoking the corresponding notification handlers. + /// + public class OpenIddictServerEventService : IOpenIddictServerEventService + { + private readonly IServiceProvider _provider; + + /// + /// Creates a new instance of the class. + /// + public OpenIddictServerEventService([NotNull] IServiceProvider provider) + => _provider = provider; + + /// + /// Publishes a new event. + /// + /// The type of the event to publish. + /// The event to publish. + /// A that can be used to monitor the asynchronous operation. + public async Task PublishAsync([NotNull] TEvent notification) where TEvent : class, IOpenIddictServerEvent + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + foreach (var handler in _provider.GetServices>()) + { + switch (await handler.HandleAsync(notification)) + { + case OpenIddictServerEventState.Unhandled: continue; + case OpenIddictServerEventState.Handled: return; + + default: throw new InvalidOperationException("The specified event state is not valid."); + } + } + } + } +} diff --git a/src/OpenIddict.Server/OpenIddictServerEventState.cs b/src/OpenIddict.Server/OpenIddictServerEventState.cs new file mode 100644 index 00000000..9873e49c --- /dev/null +++ b/src/OpenIddict.Server/OpenIddictServerEventState.cs @@ -0,0 +1,33 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +namespace OpenIddict.Server +{ + /// + /// Represents the state of an event triggered by the OpenIddict + /// server components and processed by user-defined handlers. + /// + public enum OpenIddictServerEventState + { + /// + /// Marks the event as unhandled, allowing the event service to invoke the + /// other event handlers registered in the dependency injection container. + /// Using this value is recommended for event handlers that don't produce + /// an immediate response (i.e that don't call context.HandleResponse(), + /// context.SkipHandler(), context.Validate() or context.Reject()). + /// + Unhandled = 0, + + /// + /// Marks the event as fully handled, preventing the event service from invoking + /// other event handlers registered in the dependency injection container. + /// Using this value is recommended for event handlers that produce an + /// immediate response (i.e that call context.HandleResponse(), + /// context.SkipHandler(), context.Validate() or context.Reject()). + /// + Handled = 1 + } +} diff --git a/src/OpenIddict.Server/OpenIddictServerExtensions.cs b/src/OpenIddict.Server/OpenIddictServerExtensions.cs index 9c93193f..eeb3ca40 100644 --- a/src/OpenIddict.Server/OpenIddictServerExtensions.cs +++ b/src/OpenIddict.Server/OpenIddictServerExtensions.cs @@ -42,7 +42,7 @@ namespace Microsoft.Extensions.DependencyInjection builder.Services.AddMemoryCache(); builder.Services.AddOptions(); - builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); builder.Services.TryAddScoped(); builder.Services.TryAddScoped(provider => { @@ -54,7 +54,7 @@ namespace Microsoft.Extensions.DependencyInjection return new OpenIddictServerProvider( provider.GetRequiredService>(), - provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetService() ?? throw CreateException(), provider.GetService() ?? throw CreateException(), provider.GetService() ?? throw CreateException(), diff --git a/src/OpenIddict.Validation/IOpenIddictValidationEventHandler.cs b/src/OpenIddict.Validation/IOpenIddictValidationEventHandler.cs index 9e60cc1d..2840ee43 100644 --- a/src/OpenIddict.Validation/IOpenIddictValidationEventHandler.cs +++ b/src/OpenIddict.Validation/IOpenIddictValidationEventHandler.cs @@ -4,7 +4,6 @@ * the license and the contributors participating to this project. */ -using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -20,12 +19,10 @@ namespace OpenIddict.Validation /// Processes the event. /// /// The event to process. - /// - /// 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 determines whether next handlers in the pipeline are invoked. /// - Task HandleAsync([NotNull] TEvent notification, CancellationToken cancellationToken); + Task HandleAsync([NotNull] TEvent notification); } } diff --git a/src/OpenIddict.Validation/IOpenIddictValidationEventService.cs b/src/OpenIddict.Validation/IOpenIddictValidationEventService.cs new file mode 100644 index 00000000..05b7bcf0 --- /dev/null +++ b/src/OpenIddict.Validation/IOpenIddictValidationEventService.cs @@ -0,0 +1,25 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace OpenIddict.Validation +{ + /// + /// Dispatches events by invoking the corresponding handlers. + /// + public interface IOpenIddictValidationEventService + { + /// + /// Publishes a new event. + /// + /// The type of the event to publish. + /// The event to publish. + /// A that can be used to monitor the asynchronous operation. + Task PublishAsync([NotNull] TEvent notification) where TEvent : class, IOpenIddictValidationEvent; + } +} diff --git a/src/OpenIddict.Validation/Internal/OpenIddictValidationEventService.cs b/src/OpenIddict.Validation/Internal/OpenIddictValidationEventService.cs deleted file mode 100644 index a686b661..00000000 --- a/src/OpenIddict.Validation/Internal/OpenIddictValidationEventService.cs +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) - * See https://github.com/openiddict/openiddict-core for more information concerning - * the license and the contributors participating to this project. - */ - -using System; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; - -namespace OpenIddict.Validation.Internal -{ - /// - /// Dispatches events by invoking the corresponding notification handlers. - /// Note: this API supports the OpenIddict infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future minor releases. - /// - public class OpenIddictValidationEventService - { - private readonly IServiceProvider _provider; - - /// - /// Creates a new instance of the class. - /// Note: this API supports the OpenIddict infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future minor releases. - /// - public OpenIddictValidationEventService([NotNull] IServiceProvider provider) - { - _provider = provider; - } - - /// - /// Publishes a new event. - /// - /// The type of the event to publish. - /// The event to publish. - /// The that can be used to abort the operation. - /// A that can be used to monitor the asynchronous operation. - public async Task PublishAsync([NotNull] TEvent notification, CancellationToken cancellationToken = default) - where TEvent : class, IOpenIddictValidationEvent - { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } - - foreach (var handler in _provider.GetServices>()) - { - cancellationToken.ThrowIfCancellationRequested(); - - await handler.HandleAsync(notification, cancellationToken); - - // Note: the following logic determines whether next handlers should be invoked - // depending on whether the underlying event context was substantially updated. - switch (notification) - { - case OpenIddictValidationEvents.ApplyChallenge value when value.Context.Handled: return; - - case OpenIddictValidationEvents.CreateTicket value when value.Context.Result != null: return; - case OpenIddictValidationEvents.CreateTicket value when value.Context.Principal == null: return; - - case OpenIddictValidationEvents.DecryptToken value when value.Context.Result != null: return; - case OpenIddictValidationEvents.DecryptToken value when value.Context.Principal != null: return; - - case OpenIddictValidationEvents.RetrieveToken value when value.Context.Result != null: return; - case OpenIddictValidationEvents.RetrieveToken value when value.Context.Principal != null: return; - - case OpenIddictValidationEvents.ValidateToken value when value.Context.Result != null: return; - case OpenIddictValidationEvents.ValidateToken value when value.Context.Principal == null: return; - } - } - } - } -} diff --git a/src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs b/src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs index 3efd000a..a91e7697 100644 --- a/src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs +++ b/src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs @@ -21,14 +21,14 @@ namespace OpenIddict.Validation.Internal /// public sealed class OpenIddictValidationProvider : OAuthValidationEvents { - private readonly OpenIddictValidationEventService _eventService; + private readonly IOpenIddictValidationEventService _eventService; /// /// Creates a new instance of the class. /// Note: this API supports the OpenIddict infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future minor releases. /// - public OpenIddictValidationProvider([NotNull] OpenIddictValidationEventService eventService) + public OpenIddictValidationProvider([NotNull] IOpenIddictValidationEventService eventService) => _eventService = eventService; public override Task ApplyChallenge([NotNull] ApplyChallengeContext context) diff --git a/src/OpenIddict.Validation/OpenIddict.Validation.csproj b/src/OpenIddict.Validation/OpenIddict.Validation.csproj index 2a061224..5aef6f20 100644 --- a/src/OpenIddict.Validation/OpenIddict.Validation.csproj +++ b/src/OpenIddict.Validation/OpenIddict.Validation.csproj @@ -22,4 +22,8 @@ + + + + diff --git a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs index b1850ac7..568ee62e 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs @@ -7,11 +7,10 @@ using System; using System.ComponentModel; using System.Linq; -using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.DataProtection; -using OpenIddict.Abstractions; +using OpenIddict.Extensions; using OpenIddict.Validation; namespace Microsoft.Extensions.DependencyInjection @@ -42,13 +41,13 @@ namespace Microsoft.Extensions.DependencyInjection public IServiceCollection Services { get; } /// - /// Registers an event handler for the specified event type. + /// Registers an inline event handler for the specified event type. /// - /// The handler added to the DI container. + /// The handler delegate. /// The . [EditorBrowsable(EditorBrowsableState.Advanced)] public OpenIddictValidationBuilder AddEventHandler( - [NotNull] IOpenIddictValidationEventHandler handler) + [NotNull] Func> handler) where TEvent : class, IOpenIddictValidationEvent { if (handler == null) @@ -56,62 +55,30 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(handler)); } - Services.AddSingleton(handler); + Services.AddSingleton>( + new OpenIddictValidationEventHandler(handler)); return this; } /// - /// Registers an event handler for the specified event type. + /// Registers an event handler that will be invoked for all the events listed by the implemented interfaces. /// - /// The handler added to the DI container. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictValidationBuilder AddEventHandler([NotNull] Func handler) - where TEvent : class, IOpenIddictValidationEvent - => AddEventHandler((notification, cancellationToken) => handler(notification)); - - /// - /// Registers an event handler for the specified event type. - /// - /// The handler added to the DI container. - /// The . - [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictValidationBuilder AddEventHandler([NotNull] Func handler) - where TEvent : class, IOpenIddictValidationEvent - { - if (handler == null) - { - throw new ArgumentNullException(nameof(handler)); - } - - return AddEventHandler(new OpenIddictValidationEventHandler(handler)); - } - - /// - /// Registers an event handler for the specified event type. - /// - /// The type of the event. /// The type of the handler. /// The lifetime of the registered service. /// The . [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictValidationBuilder AddEventHandler( - ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TEvent : class, IOpenIddictValidationEvent - where THandler : IOpenIddictValidationEventHandler - => AddEventHandler(typeof(THandler)); + public OpenIddictValidationBuilder AddEventHandler(ServiceLifetime lifetime = ServiceLifetime.Scoped) + => AddEventHandler(typeof(THandler), lifetime); /// - /// Registers an event handler for the specified event type. + /// Registers an event handler that will be invoked for all the events listed by the implemented interfaces. /// /// The type of the handler. /// The lifetime of the registered service. /// The . [EditorBrowsable(EditorBrowsableState.Advanced)] - public OpenIddictValidationBuilder AddEventHandler( - [NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) - where TEvent : class, IOpenIddictValidationEvent + public OpenIddictValidationBuilder AddEventHandler([NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped) { if (type == null) { @@ -123,12 +90,21 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentException("Handlers cannot be registered as transient services.", nameof(lifetime)); } - if (!typeof(IOpenIddictValidationEventHandler).IsAssignableFrom(type)) + if (type.IsGenericTypeDefinition) { throw new ArgumentException("The specified type is invalid.", nameof(type)); } - Services.Add(new ServiceDescriptor(typeof(IOpenIddictValidationEventHandler), type, lifetime)); + var services = OpenIddictHelpers.FindGenericBaseTypes(type, typeof(IOpenIddictValidationEventHandler<>)).ToArray(); + if (services.Length == 0) + { + throw new ArgumentException("The specified type is invalid.", nameof(type)); + } + + foreach (var service in services) + { + Services.Add(new ServiceDescriptor(service, type, lifetime)); + } return this; } diff --git a/src/OpenIddict.Validation/OpenIddictValidationEventHandler.cs b/src/OpenIddict.Validation/OpenIddictValidationEventHandler.cs index c41f3e39..b3152c75 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationEventHandler.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationEventHandler.cs @@ -5,7 +5,6 @@ */ using System; -using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -18,38 +17,24 @@ namespace OpenIddict.Validation public class OpenIddictValidationEventHandler : IOpenIddictValidationEventHandler where TEvent : class, IOpenIddictValidationEvent { - private readonly Func _handler; + private readonly Func> _handler; /// /// Creates a new event using the specified handler delegate. /// - /// The event handler delegate - public OpenIddictValidationEventHandler([NotNull] Func handler) + /// The event handler delegate. + public OpenIddictValidationEventHandler([NotNull] Func> handler) => _handler = handler ?? throw new ArgumentNullException(nameof(handler)); /// /// Processes the event. /// /// The event to process. - /// - /// 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 determines whether next handlers in the pipeline are invoked. /// - public Task HandleAsync(TEvent notification, CancellationToken cancellationToken) - { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } - - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - return _handler.Invoke(notification, cancellationToken); - } + public Task HandleAsync(TEvent notification) + => _handler(notification ?? throw new ArgumentNullException(nameof(notification))); } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationEventService.cs b/src/OpenIddict.Validation/OpenIddictValidationEventService.cs new file mode 100644 index 00000000..e2f09056 --- /dev/null +++ b/src/OpenIddict.Validation/OpenIddictValidationEventService.cs @@ -0,0 +1,52 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace OpenIddict.Validation +{ + /// + /// Dispatches events by invoking the corresponding notification handlers. + /// + public class OpenIddictValidationEventService : IOpenIddictValidationEventService + { + private readonly IServiceProvider _provider; + + /// + /// Creates a new instance of the class. + /// + public OpenIddictValidationEventService([NotNull] IServiceProvider provider) + => _provider = provider; + + /// + /// Publishes a new event. + /// + /// The type of the event to publish. + /// The event to publish. + /// A that can be used to monitor the asynchronous operation. + public async Task PublishAsync([NotNull] TEvent notification) where TEvent : class, IOpenIddictValidationEvent + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + foreach (var handler in _provider.GetServices>()) + { + switch (await handler.HandleAsync(notification)) + { + case OpenIddictValidationEventState.Unhandled: continue; + case OpenIddictValidationEventState.Handled: return; + + default: throw new InvalidOperationException("The specified event state is not valid."); + } + } + } + } +} diff --git a/src/OpenIddict.Validation/OpenIddictValidationEventState.cs b/src/OpenIddict.Validation/OpenIddictValidationEventState.cs new file mode 100644 index 00000000..b3b47569 --- /dev/null +++ b/src/OpenIddict.Validation/OpenIddictValidationEventState.cs @@ -0,0 +1,33 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +namespace OpenIddict.Validation +{ + /// + /// Represents the state of an event triggered by the OpenIddict + /// validation components and processed by user-defined handlers. + /// + public enum OpenIddictValidationEventState + { + /// + /// Marks the event as unhandled, allowing the event service to invoke the + /// other event handlers registered in the dependency injection container. + /// Using this value is recommended for event handlers that don't produce + /// an immediate response (i.e that don't call context.HandleResponse(), + /// context.Fail(), context.NoResult() or context.Success()). + /// + Unhandled = 0, + + /// + /// Marks the event as fully handled, preventing the event service from invoking + /// other event handlers registered in the dependency injection container. + /// Using this value is recommended for event handlers that produce an + /// immediate response (i.e that call context.HandleResponse(), + /// context.Fail(), context.NoResult() or context.Success()). + /// + Handled = 1 + } +} diff --git a/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs b/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs index ae7d9bd2..202f4a9e 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs @@ -41,7 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection builder.Services.AddLogging(); builder.Services.AddOptions(); - builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); builder.Services.TryAddScoped(); builder.Services.TryAddScoped(); diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs index c0689983..02bcb1b8 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs @@ -42,20 +42,18 @@ namespace OpenIddict.Server.Tests // Arrange var services = CreateServices(); var builder = CreateBuilder(services); - var handler = new OpenIddictServerEventHandler( - (notification, cancellationToken) => Task.CompletedTask); // Act - builder.AddEventHandler(handler); + builder.AddEventHandler(notification => Task.FromResult(OpenIddictServerEventState.Handled)); // Assert Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictServerEventHandler) && - service.ImplementationInstance == handler); + service.ImplementationInstance.GetType() == typeof(OpenIddictServerEventHandler)); } [Fact] - public void AddEventHandler_ThrowsAnExceptionForInvalidHandlerType() + public void AddEventHandler_ThrowsAnExceptionForUnsupportedLifetime() { // Arrange var services = CreateServices(); @@ -64,7 +62,41 @@ namespace OpenIddict.Server.Tests // Act and assert var exception = Assert.Throws(delegate { - return builder.AddEventHandler(typeof(object)); + return builder.AddEventHandler(ServiceLifetime.Transient); + }); + + Assert.Equal("lifetime", exception.ParamName); + Assert.StartsWith("Handlers cannot be registered as transient services.", exception.Message); + } + + [Fact] + public void AddEventHandler_ThrowsAnExceptionForOpenGenericHandlerType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate + { + return builder.AddEventHandler(typeof(OpenIddictServerEventHandler<>)); + }); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void AddEventHandler_ThrowsAnExceptionForNonHandlerType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate + { + return builder.AddEventHandler(typeof(object)); }); Assert.Equal("type", exception.ParamName); @@ -79,12 +111,17 @@ namespace OpenIddict.Server.Tests var builder = CreateBuilder(services); // Act - builder.AddEventHandler(); + builder.AddEventHandler(ServiceLifetime.Singleton); // Assert Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictServerEventHandler) && - service.ImplementationType == typeof(CustomHandler)); + service.ImplementationType == typeof(CustomHandler) && + service.Lifetime == ServiceLifetime.Singleton); + Assert.Contains(services, service => + service.ServiceType == typeof(IOpenIddictServerEventHandler) && + service.ImplementationType == typeof(CustomHandler) && + service.Lifetime == ServiceLifetime.Singleton); } [Fact] @@ -873,10 +910,17 @@ namespace OpenIddict.Server.Tests return options.Get(OpenIddictServerDefaults.AuthenticationScheme); } - public class CustomHandler : OpenIddictServerEventHandler + public class CustomHandler : IOpenIddictServerEventHandler, + IOpenIddictServerEventHandler { - public CustomHandler(Func handler) : base(handler) + public Task HandleAsync(ApplyAuthorizationResponse notification) + { + throw new NotImplementedException(); + } + + public Task HandleAsync(HandleAuthorizationRequest notification) { + throw new NotImplementedException(); } } } diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerEventHandlerTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerEventHandlerTests.cs new file mode 100644 index 00000000..eb24c800 --- /dev/null +++ b/test/OpenIddict.Server.Tests/OpenIddictServerEventHandlerTests.cs @@ -0,0 +1,60 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Threading.Tasks; +using Xunit; + +namespace OpenIddict.Server.Tests +{ + public class OpenIddictServerEventHandlerTests + { + [Fact] + public void Constructor_ThrowsAnExceptionForNullHandler() + { + // Arrange, act and assert + var exception = Assert.Throws(() + => new OpenIddictServerEventHandler(handler: null)); + + Assert.Equal("handler", exception.ParamName); + } + + [Fact] + public async Task HandleAsync_ThrowsAnExceptionForNullNotification() + { + // Arrange + var handler = new OpenIddictServerEventHandler( + notification => Task.FromResult(OpenIddictServerEventState.Handled)); + + // Act and assert + var exception = await Assert.ThrowsAsync(() + => handler.HandleAsync(notification: null)); + + Assert.Equal("notification", exception.ParamName); + } + + [Fact] + public async Task HandleAsync_InvokesInlineHandler() + { + // Arrange + var marker = false; + var handler = new OpenIddictServerEventHandler( + notification => + { + marker = true; + return Task.FromResult(OpenIddictServerEventState.Handled); + }); + + // Act + await handler.HandleAsync(new Event()); + + // Assert + Assert.True(marker); + } + + public class Event : IOpenIddictServerEvent { } + } +} diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerEventServiceTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerEventServiceTests.cs new file mode 100644 index 00000000..6d3ecbc0 --- /dev/null +++ b/test/OpenIddict.Server.Tests/OpenIddictServerEventServiceTests.cs @@ -0,0 +1,92 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Moq; +using Xunit; + +namespace OpenIddict.Server.Tests +{ + public class OpenIddictServerEventServiceTests + { + [Fact] + public async Task PublishAsync_ThrowsAnExceptionForNullNotification() + { + // Arrange + var provider = Mock.Of(); + var service = new OpenIddictServerEventService(provider); + + // Act and assert + var exception = await Assert.ThrowsAsync(() + => service.PublishAsync(notification: null)); + + Assert.Equal("notification", exception.ParamName); + } + + [Fact] + public async Task PublishAsync_InvokesHandlers() + { + // Arrange + var handlers = new List> + { + Mock.Of>(), + Mock.Of>() + }; + + var provider = new Mock(); + provider.Setup(mock => mock.GetService(typeof(IEnumerable>))) + .Returns(handlers); + + var service = new OpenIddictServerEventService(provider.Object); + + var notification = new Event(); + + // Act + await service.PublishAsync(notification); + + // Assert + Mock.Get(handlers[0]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + Mock.Get(handlers[1]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + } + + [Fact] + public async Task PublishAsync_StopsInvokingHandlersWhenHandledIsReturned() + { + // Arrange + var handlers = new List> + { + Mock.Of>( + mock => mock.HandleAsync(It.IsAny()) == Task.FromResult(OpenIddictServerEventState.Unhandled)), + Mock.Of>( + mock => mock.HandleAsync(It.IsAny()) == Task.FromResult(OpenIddictServerEventState.Unhandled)), + Mock.Of>( + mock => mock.HandleAsync(It.IsAny()) == Task.FromResult(OpenIddictServerEventState.Handled)), + Mock.Of>() + }; + + var provider = new Mock(); + provider.Setup(mock => mock.GetService(typeof(IEnumerable>))) + .Returns(handlers); + + var service = new OpenIddictServerEventService(provider.Object); + + var notification = new Event(); + + // Act + await service.PublishAsync(notification); + + // Assert + Mock.Get(handlers[0]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + Mock.Get(handlers[1]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + Mock.Get(handlers[2]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + Mock.Get(handlers[3]).Verify(mock => mock.HandleAsync(notification), Times.Never()); + } + + public class Event : IOpenIddictServerEvent { } + } +} diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs index baa4f663..2924583a 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs @@ -114,7 +114,7 @@ namespace OpenIddict.Server.Tests // Assert Assert.Contains(services, service => service.Lifetime == ServiceLifetime.Scoped && - service.ServiceType == typeof(OpenIddictServerEventService) && + service.ServiceType == typeof(IOpenIddictServerEventService) && service.ImplementationType == typeof(OpenIddictServerEventService)); } diff --git a/test/OpenIddict.Validation.Tests/OpenIddictValidationBuilderTests.cs b/test/OpenIddict.Validation.Tests/OpenIddictValidationBuilderTests.cs index e1738ff5..35d4926c 100644 --- a/test/OpenIddict.Validation.Tests/OpenIddictValidationBuilderTests.cs +++ b/test/OpenIddict.Validation.Tests/OpenIddictValidationBuilderTests.cs @@ -35,20 +35,18 @@ namespace OpenIddict.Validation.Tests // Arrange var services = CreateServices(); var builder = CreateBuilder(services); - var handler = new OpenIddictValidationEventHandler( - (notification, cancellationToken) => Task.CompletedTask); // Act - builder.AddEventHandler(handler); + builder.AddEventHandler(notification => Task.FromResult(OpenIddictValidationEventState.Handled)); // Assert Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictValidationEventHandler) && - service.ImplementationInstance == handler); + service.ImplementationInstance.GetType() == typeof(OpenIddictValidationEventHandler)); } [Fact] - public void AddEventHandler_ThrowsAnExceptionForInvalidHandlerType() + public void AddEventHandler_ThrowsAnExceptionForUnsupportedLifetime() { // Arrange var services = CreateServices(); @@ -57,7 +55,41 @@ namespace OpenIddict.Validation.Tests // Act and assert var exception = Assert.Throws(delegate { - return builder.AddEventHandler(typeof(object)); + return builder.AddEventHandler(ServiceLifetime.Transient); + }); + + Assert.Equal("lifetime", exception.ParamName); + Assert.StartsWith("Handlers cannot be registered as transient services.", exception.Message); + } + + [Fact] + public void AddEventHandler_ThrowsAnExceptionForOpenGenericHandlerType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate + { + return builder.AddEventHandler(typeof(OpenIddictValidationEventHandler<>)); + }); + + Assert.Equal("type", exception.ParamName); + Assert.StartsWith("The specified type is invalid.", exception.Message); + } + + [Fact] + public void AddEventHandler_ThrowsAnExceptionForNonHandlerType() + { + // Arrange + var services = CreateServices(); + var builder = CreateBuilder(services); + + // Act and assert + var exception = Assert.Throws(delegate + { + return builder.AddEventHandler(typeof(object)); }); Assert.Equal("type", exception.ParamName); @@ -72,12 +104,17 @@ namespace OpenIddict.Validation.Tests var builder = CreateBuilder(services); // Act - builder.AddEventHandler(); + builder.AddEventHandler(ServiceLifetime.Singleton); // Assert + Assert.Contains(services, service => + service.ServiceType == typeof(IOpenIddictValidationEventHandler) && + service.ImplementationType == typeof(CustomHandler) && + service.Lifetime == ServiceLifetime.Singleton); Assert.Contains(services, service => service.ServiceType == typeof(IOpenIddictValidationEventHandler) && - service.ImplementationType == typeof(CustomHandler)); + service.ImplementationType == typeof(CustomHandler) && + service.Lifetime == ServiceLifetime.Singleton); } [Fact] @@ -205,10 +242,17 @@ namespace OpenIddict.Validation.Tests return options.Get(OpenIddictValidationDefaults.AuthenticationScheme); } - public class CustomHandler : OpenIddictValidationEventHandler + public class CustomHandler : IOpenIddictValidationEventHandler, + IOpenIddictValidationEventHandler { - public CustomHandler(Func handler) : base(handler) + public Task HandleAsync(ApplyChallenge notification) + { + throw new NotImplementedException(); + } + + public Task HandleAsync(CreateTicket notification) { + throw new NotImplementedException(); } } } diff --git a/test/OpenIddict.Validation.Tests/OpenIddictValidationEventHandlerTests.cs b/test/OpenIddict.Validation.Tests/OpenIddictValidationEventHandlerTests.cs new file mode 100644 index 00000000..279352dc --- /dev/null +++ b/test/OpenIddict.Validation.Tests/OpenIddictValidationEventHandlerTests.cs @@ -0,0 +1,60 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Threading.Tasks; +using Xunit; + +namespace OpenIddict.Validation.Tests +{ + public class OpenIddictValidationEventHandlerTests + { + [Fact] + public void Constructor_ThrowsAnExceptionForNullHandler() + { + // Arrange, act and assert + var exception = Assert.Throws(() + => new OpenIddictValidationEventHandler(handler: null)); + + Assert.Equal("handler", exception.ParamName); + } + + [Fact] + public async Task HandleAsync_ThrowsAnExceptionForNullNotification() + { + // Arrange + var handler = new OpenIddictValidationEventHandler( + notification => Task.FromResult(OpenIddictValidationEventState.Handled)); + + // Act and assert + var exception = await Assert.ThrowsAsync(() + => handler.HandleAsync(notification: null)); + + Assert.Equal("notification", exception.ParamName); + } + + [Fact] + public async Task HandleAsync_InvokesInlineHandler() + { + // Arrange + var marker = false; + var handler = new OpenIddictValidationEventHandler( + notification => + { + marker = true; + return Task.FromResult(OpenIddictValidationEventState.Handled); + }); + + // Act + await handler.HandleAsync(new Event()); + + // Assert + Assert.True(marker); + } + + public class Event : IOpenIddictValidationEvent { } + } +} diff --git a/test/OpenIddict.Validation.Tests/OpenIddictValidationEventServiceTests.cs b/test/OpenIddict.Validation.Tests/OpenIddictValidationEventServiceTests.cs new file mode 100644 index 00000000..b066f523 --- /dev/null +++ b/test/OpenIddict.Validation.Tests/OpenIddictValidationEventServiceTests.cs @@ -0,0 +1,92 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Moq; +using Xunit; + +namespace OpenIddict.Validation.Tests +{ + public class OpenIddictValidationEventServiceTests + { + [Fact] + public async Task PublishAsync_ThrowsAnExceptionForNullNotification() + { + // Arrange + var provider = Mock.Of(); + var service = new OpenIddictValidationEventService(provider); + + // Act and assert + var exception = await Assert.ThrowsAsync(() + => service.PublishAsync(notification: null)); + + Assert.Equal("notification", exception.ParamName); + } + + [Fact] + public async Task PublishAsync_InvokesHandlers() + { + // Arrange + var handlers = new List> + { + Mock.Of>(), + Mock.Of>() + }; + + var provider = new Mock(); + provider.Setup(mock => mock.GetService(typeof(IEnumerable>))) + .Returns(handlers); + + var service = new OpenIddictValidationEventService(provider.Object); + + var notification = new Event(); + + // Act + await service.PublishAsync(notification); + + // Assert + Mock.Get(handlers[0]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + Mock.Get(handlers[1]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + } + + [Fact] + public async Task PublishAsync_StopsInvokingHandlersWhenHandledIsReturned() + { + // Arrange + var handlers = new List> + { + Mock.Of>( + mock => mock.HandleAsync(It.IsAny()) == Task.FromResult(OpenIddictValidationEventState.Unhandled)), + Mock.Of>( + mock => mock.HandleAsync(It.IsAny()) == Task.FromResult(OpenIddictValidationEventState.Unhandled)), + Mock.Of>( + mock => mock.HandleAsync(It.IsAny()) == Task.FromResult(OpenIddictValidationEventState.Handled)), + Mock.Of>() + }; + + var provider = new Mock(); + provider.Setup(mock => mock.GetService(typeof(IEnumerable>))) + .Returns(handlers); + + var service = new OpenIddictValidationEventService(provider.Object); + + var notification = new Event(); + + // Act + await service.PublishAsync(notification); + + // Assert + Mock.Get(handlers[0]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + Mock.Get(handlers[1]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + Mock.Get(handlers[2]).Verify(mock => mock.HandleAsync(notification), Times.Once()); + Mock.Get(handlers[3]).Verify(mock => mock.HandleAsync(notification), Times.Never()); + } + + public class Event : IOpenIddictValidationEvent { } + } +} diff --git a/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs b/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs index 44702f99..d0f92f00 100644 --- a/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs +++ b/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs @@ -97,7 +97,7 @@ namespace OpenIddict.Validation.Tests // Assert Assert.Contains(services, service => service.Lifetime == ServiceLifetime.Scoped && - service.ServiceType == typeof(OpenIddictValidationEventService) && + service.ServiceType == typeof(IOpenIddictValidationEventService) && service.ImplementationType == typeof(OpenIddictValidationEventService)); }