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 70% rename from src/OpenIddict.Core/OpenIddictCoreHelpers.cs rename to shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs index 65090289..8026a646 100644 --- a/src/OpenIddict.Core/OpenIddictCoreHelpers.cs +++ b/shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs @@ -1,23 +1,31 @@ using System; -using System.ComponentModel; +using System.Collections.Generic; using System.Linq; using System.Reflection; -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) { @@ -45,7 +53,7 @@ namespace OpenIddict.Core if (contract.GetGenericTypeDefinition() == definition) { - return contract; + yield return contract; } } } @@ -61,12 +69,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..e613eb64 --- /dev/null +++ b/shared/OpenIddict.Extensions/OpenIddict.Extensions.csproj @@ -0,0 +1,14 @@ + + + + + + netstandard1.3 + false + + + + + + + diff --git a/src/OpenIddict.Core/OpenIddict.Core.csproj b/src/OpenIddict.Core/OpenIddict.Core.csproj index a657d1f3..70acc746 100644 --- a/src/OpenIddict.Core/OpenIddict.Core.csproj +++ b/src/OpenIddict.Core/OpenIddict.Core.csproj @@ -30,4 +30,8 @@ + + + + diff --git a/src/OpenIddict.Core/OpenIddictCoreBuilder.cs b/src/OpenIddict.Core/OpenIddictCoreBuilder.cs index 9c8a4ec4..9fdff98c 100644 --- a/src/OpenIddict.Core/OpenIddictCoreBuilder.cs +++ b/src/OpenIddict.Core/OpenIddictCoreBuilder.cs @@ -11,6 +11,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection.Extensions; using OpenIddict.Abstractions; using OpenIddict.Core; +using OpenIddict.Extensions; namespace Microsoft.Extensions.DependencyInjection { @@ -87,7 +88,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)); @@ -144,7 +145,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)); @@ -201,7 +202,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)); @@ -258,7 +259,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)); @@ -315,7 +316,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)); @@ -412,7 +413,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)); @@ -508,7 +509,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)); @@ -605,7 +606,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 73683e1a..460385f8 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 a5937e77..dee8b6f9 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 217266cd..2ce3caee 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 52bfb7f3..55dd56a6 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 74730a68..3110f8dc 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 ad1e45bc..268458b2 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 c6243669..f648f2fa 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 ea198cf0..2ae6ce18 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 fa8f548c..6c072d00 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 74b58473..c9edcc49 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 ce91a4b1..00000000 --- a/src/OpenIddict.Server/Internal/OpenIddictServerEventService.cs +++ /dev/null @@ -1,164 +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.AspNetCore.Authentication; -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.State != EventResultState.Continue: 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.State != EventResultState.Continue: return; - case ExtractConfigurationRequest value when value.Context.State != EventResultState.Continue: return; - case ExtractCryptographyRequest value when value.Context.State != EventResultState.Continue: return; - case ExtractIntrospectionRequest value when value.Context.State != EventResultState.Continue: return; - case ExtractLogoutRequest value when value.Context.State != EventResultState.Continue: return; - case ExtractRevocationRequest value when value.Context.State != EventResultState.Continue: return; - case ExtractTokenRequest value when value.Context.State != EventResultState.Continue: return; - case ExtractUserinfoRequest value when value.Context.State != EventResultState.Continue: return; - - case ValidateAuthorizationRequest value when value.Context.State != EventResultState.Continue: return; - case ValidateConfigurationRequest value when value.Context.State != EventResultState.Continue: return; - case ValidateCryptographyRequest value when value.Context.State != EventResultState.Continue: return; - case ValidateIntrospectionRequest value when value.Context.State != EventResultState.Continue: return; - case ValidateLogoutRequest value when value.Context.State != EventResultState.Continue: return; - case ValidateRevocationRequest value when value.Context.State != EventResultState.Continue: return; - case ValidateTokenRequest value when value.Context.State != EventResultState.Continue: return; - case ValidateUserinfoRequest value when value.Context.State != EventResultState.Continue: 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.State != EventResultState.Continue: return; - case HandleConfigurationRequest value when value.Context.State != EventResultState.Continue: return; - case HandleCryptographyRequest value when value.Context.State != EventResultState.Continue: return; - case HandleIntrospectionRequest value when value.Context.State != EventResultState.Continue: return; - case HandleLogoutRequest value when value.Context.State != EventResultState.Continue: return; - case HandleRevocationRequest value when value.Context.State != EventResultState.Continue: return; - case HandleTokenRequest value when value.Context.State != EventResultState.Continue: return; - case HandleUserinfoRequest value when value.Context.State != EventResultState.Continue: 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.State != EventResultState.Continue: return; - case ProcessSigninResponse value when value.Context.State != EventResultState.Continue: return; - case ProcessSignoutResponse value when value.Context.State != EventResultState.Continue: 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.State != EventResultState.Continue: return; - case ApplyConfigurationResponse value when value.Context.State != EventResultState.Continue: return; - case ApplyCryptographyResponse value when value.Context.State != EventResultState.Continue: return; - case ApplyIntrospectionResponse value when value.Context.State != EventResultState.Continue: return; - case ApplyLogoutResponse value when value.Context.State != EventResultState.Continue: return; - case ApplyRevocationResponse value when value.Context.State != EventResultState.Continue: return; - case ApplyTokenResponse value when value.Context.State != EventResultState.Continue: return; - case ApplyUserinfoResponse value when value.Context.State != EventResultState.Continue: return; - - case DeserializeAuthorizationCode value when value.Context.State != EventResultState.Continue: return; - case DeserializeAccessToken value when value.Context.State != EventResultState.Continue: return; - case DeserializeIdentityToken value when value.Context.State != EventResultState.Continue: return; - case DeserializeRefreshToken value when value.Context.State != EventResultState.Continue: 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.State != EventResultState.Continue: return; - case SerializeAccessToken value when value.Context.State != EventResultState.Continue: return; - case SerializeIdentityToken value when value.Context.State != EventResultState.Continue: return; - case SerializeRefreshToken value when value.Context.State != EventResultState.Continue: 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.Helpers.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs index d038223d..2490132e 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs @@ -684,8 +684,8 @@ namespace OpenIddict.Server.Internal private static ILogger GetLogger(IServiceProvider provider) => provider.GetRequiredService>(); - private static OpenIddictServerEventService GetEventService(IServiceProvider provider) - => provider.GetRequiredService(); + private static IOpenIddictServerEventService GetEventService(IServiceProvider provider) + => provider.GetRequiredService(); private static IOpenIddictApplicationManager GetApplicationManager(IServiceProvider provider) => provider.GetService() ?? throw new InvalidOperationException(new StringBuilder() diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs index d29ef561..6fd34027 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; diff --git a/src/OpenIddict.Server/OpenIddict.Server.csproj b/src/OpenIddict.Server/OpenIddict.Server.csproj index b190854c..5c19fc3d 100644 --- a/src/OpenIddict.Server/OpenIddict.Server.csproj +++ b/src/OpenIddict.Server/OpenIddict.Server.csproj @@ -24,4 +24,8 @@ + + + + diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs index 9754a454..855d8ab6 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; @@ -21,6 +20,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 @@ -51,13 +51,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) @@ -65,62 +65,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) { @@ -132,12 +100,21 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentException("Handlers cannot be registered as transient services.", nameof(lifetime)); } - if (!typeof(IOpenIddictServerEventHandler).IsAssignableFrom(type)) + if (type.GetTypeInfo().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 ef6aaf68..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,35 +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 async Task HandleAsync(TEvent notification, CancellationToken cancellationToken) - { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - await _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..37145e2c --- /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.SkipToNextMiddleware(), 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.SkipToNextMiddleware(), + /// context.HandleResponse(), context.Validate() or context.Reject()). + /// + Handled = 1 + } +} diff --git a/src/OpenIddict.Server/OpenIddictServerExtensions.cs b/src/OpenIddict.Server/OpenIddictServerExtensions.cs index 64c319ce..017579f6 100644 --- a/src/OpenIddict.Server/OpenIddictServerExtensions.cs +++ b/src/OpenIddict.Server/OpenIddictServerExtensions.cs @@ -46,7 +46,7 @@ namespace Microsoft.Extensions.DependencyInjection builder.Services.AddMemoryCache(); builder.Services.AddOptions(); - builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); return new OpenIddictServerBuilder(builder.Services); } 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 ca51267b..00000000 --- a/src/OpenIddict.Validation/Internal/OpenIddictValidationEventService.cs +++ /dev/null @@ -1,77 +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.AspNetCore.Authentication; -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.State != EventResultState.Continue: return; - - case OpenIddictValidationEvents.CreateTicket value when value.Context.State != EventResultState.Continue: return; - case OpenIddictValidationEvents.CreateTicket value when value.Context.Ticket == null: return; - - case OpenIddictValidationEvents.DecryptToken value when value.Context.State != EventResultState.Continue: return; - case OpenIddictValidationEvents.DecryptToken value when value.Context.Ticket != null: return; - - case OpenIddictValidationEvents.RetrieveToken value when value.Context.State != EventResultState.Continue: return; - case OpenIddictValidationEvents.RetrieveToken value when value.Context.Ticket != null: return; - - case OpenIddictValidationEvents.ValidateToken value when value.Context.State != EventResultState.Continue: return; - case OpenIddictValidationEvents.ValidateToken value when value.Context.Ticket == null: return; - } - } - } - } -} diff --git a/src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs b/src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs index ee9167e2..ec83d6d1 100644 --- a/src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs +++ b/src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs @@ -23,11 +23,11 @@ namespace OpenIddict.Validation.Internal public sealed class OpenIddictValidationProvider : OAuthValidationEvents { public override Task ApplyChallenge([NotNull] ApplyChallengeContext context) - => context.HttpContext.RequestServices.GetRequiredService() + => GetEventService(context.HttpContext.RequestServices) .PublishAsync(new OpenIddictValidationEvents.ApplyChallenge(context)); public override Task CreateTicket([NotNull] CreateTicketContext context) - => context.HttpContext.RequestServices.GetRequiredService() + => GetEventService(context.HttpContext.RequestServices) .PublishAsync(new OpenIddictValidationEvents.CreateTicket(context)); public override async Task DecryptToken([NotNull] DecryptTokenContext context) @@ -47,7 +47,7 @@ namespace OpenIddict.Validation.Internal .ToString()); } - var logger = context.HttpContext.RequestServices.GetRequiredService>(); + var logger = GetLogger(context.HttpContext.RequestServices); // Retrieve the token entry from the database. If it // cannot be found, assume the token is not valid. @@ -103,12 +103,12 @@ namespace OpenIddict.Validation.Internal context.HandleResponse(); } - await context.HttpContext.RequestServices.GetRequiredService() + await GetEventService(context.HttpContext.RequestServices) .PublishAsync(new OpenIddictValidationEvents.DecryptToken(context)); } public override Task RetrieveToken([NotNull] RetrieveTokenContext context) - => context.HttpContext.RequestServices.GetRequiredService() + => GetEventService(context.HttpContext.RequestServices) .PublishAsync(new OpenIddictValidationEvents.RetrieveToken(context)); public override async Task ValidateToken([NotNull] ValidateTokenContext context) @@ -128,7 +128,7 @@ namespace OpenIddict.Validation.Internal .ToString()); } - var logger = context.HttpContext.RequestServices.GetRequiredService>(); + var logger = GetLogger(context.HttpContext.RequestServices); var identifier = context.Ticket.Properties.GetProperty(OpenIddictConstants.Properties.InternalAuthorizationId); if (!string.IsNullOrEmpty(identifier)) @@ -145,8 +145,14 @@ namespace OpenIddict.Validation.Internal } } - await context.HttpContext.RequestServices.GetRequiredService() + await GetEventService(context.HttpContext.RequestServices) .PublishAsync(new OpenIddictValidationEvents.ValidateToken(context)); } + + private static ILogger GetLogger(IServiceProvider provider) + => provider.GetRequiredService>(); + + private static IOpenIddictValidationEventService GetEventService(IServiceProvider provider) + => provider.GetRequiredService(); } } diff --git a/src/OpenIddict.Validation/OpenIddict.Validation.csproj b/src/OpenIddict.Validation/OpenIddict.Validation.csproj index 0847e0f1..d888938d 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 d7fb9d18..c61ff7ef 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs @@ -8,10 +8,10 @@ using System; using System.ComponentModel; using System.Linq; using System.Reflection; -using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.DataProtection; +using OpenIddict.Extensions; using OpenIddict.Validation; namespace Microsoft.Extensions.DependencyInjection @@ -42,13 +42,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 +56,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 +91,21 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentException("Handlers cannot be registered as transient services.", nameof(lifetime)); } - if (!typeof(IOpenIddictValidationEventHandler).IsAssignableFrom(type)) + if (type.GetTypeInfo().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 e43b265d..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,35 +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 async Task HandleAsync(TEvent notification, CancellationToken cancellationToken) - { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - await _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 6b8799e2..717905f1 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs @@ -42,7 +42,7 @@ namespace Microsoft.Extensions.DependencyInjection builder.Services.AddLogging(); builder.Services.AddOptions(); - builder.Services.TryAddScoped(); + builder.Services.TryAddScoped(); return new OpenIddictValidationBuilder(builder.Services); } diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs index 91740620..a7c00772 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs @@ -7,7 +7,6 @@ using System; using System.IdentityModel.Tokens.Jwt; using System.Reflection; -using System.Threading; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Primitives; using Microsoft.AspNetCore.DataProtection; @@ -42,20 +41,18 @@ namespace OpenIddict.Server.Tests // Arrange var services = CreateServices(); var builder = CreateBuilder(services); - var handler = new OpenIddictServerEventHandler( - (notification, cancellationToken) => Task.FromResult(0)); // 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 +61,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 +110,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] @@ -802,10 +838,17 @@ namespace OpenIddict.Server.Tests return options.Value; } - 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 8e1183ee..79c492a4 100644 --- a/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs +++ b/test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs @@ -105,7 +105,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 1ced0ed2..cb75311a 100644 --- a/test/OpenIddict.Validation.Tests/OpenIddictValidationBuilderTests.cs +++ b/test/OpenIddict.Validation.Tests/OpenIddictValidationBuilderTests.cs @@ -5,7 +5,6 @@ */ using System; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.DependencyInjection; @@ -35,20 +34,18 @@ namespace OpenIddict.Validation.Tests // Arrange var services = CreateServices(); var builder = CreateBuilder(services); - var handler = new OpenIddictValidationEventHandler( - (notification, cancellationToken) => Task.FromResult(0)); // 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 +54,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 +103,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] @@ -204,10 +240,17 @@ namespace OpenIddict.Validation.Tests return provider.GetRequiredService>().Value; } - 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 8c6624f0..4291a248 100644 --- a/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs +++ b/test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs @@ -83,7 +83,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)); }