Browse Source

Tweak the events model to force user-defined handlers to explicitly determine whether other handlers can be invoked

pull/662/head
Kévin Chalet 8 years ago
parent
commit
9a06891507
  1. 11
      OpenIddict.sln
  2. 25
      shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs
  3. 10
      shared/OpenIddict.Extensions/OpenIddict.Extensions.csproj
  4. 4
      src/OpenIddict.Core/OpenIddict.Core.csproj
  5. 17
      src/OpenIddict.Core/OpenIddictCoreBuilder.cs
  6. 6
      src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj
  7. 4
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs
  8. 4
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs
  9. 4
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs
  10. 4
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs
  11. 4
      src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj
  12. 4
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs
  13. 5
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs
  14. 4
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs
  15. 4
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs
  16. 9
      src/OpenIddict.Server/IOpenIddictServerEventHandler.cs
  17. 26
      src/OpenIddict.Server/IOpenIddictServerEventService.cs
  18. 163
      src/OpenIddict.Server/Internal/OpenIddictServerEventService.cs
  19. 5
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs
  20. 4
      src/OpenIddict.Server/OpenIddict.Server.csproj
  21. 67
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  22. 29
      src/OpenIddict.Server/OpenIddictServerEventHandler.cs
  23. 52
      src/OpenIddict.Server/OpenIddictServerEventService.cs
  24. 33
      src/OpenIddict.Server/OpenIddictServerEventState.cs
  25. 4
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  26. 9
      src/OpenIddict.Validation/IOpenIddictValidationEventHandler.cs
  27. 25
      src/OpenIddict.Validation/IOpenIddictValidationEventService.cs
  28. 76
      src/OpenIddict.Validation/Internal/OpenIddictValidationEventService.cs
  29. 4
      src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs
  30. 4
      src/OpenIddict.Validation/OpenIddict.Validation.csproj
  31. 68
      src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
  32. 29
      src/OpenIddict.Validation/OpenIddictValidationEventHandler.cs
  33. 52
      src/OpenIddict.Validation/OpenIddictValidationEventService.cs
  34. 33
      src/OpenIddict.Validation/OpenIddictValidationEventState.cs
  35. 2
      src/OpenIddict.Validation/OpenIddictValidationExtensions.cs
  36. 64
      test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
  37. 60
      test/OpenIddict.Server.Tests/OpenIddictServerEventHandlerTests.cs
  38. 92
      test/OpenIddict.Server.Tests/OpenIddictServerEventServiceTests.cs
  39. 2
      test/OpenIddict.Server.Tests/OpenIddictServerExtensionsTests.cs
  40. 64
      test/OpenIddict.Validation.Tests/OpenIddictValidationBuilderTests.cs
  41. 60
      test/OpenIddict.Validation.Tests/OpenIddictValidationEventHandlerTests.cs
  42. 92
      test/OpenIddict.Validation.Tests/OpenIddictValidationEventServiceTests.cs
  43. 2
      test/OpenIddict.Validation.Tests/OpenIddictValidationExtensionsTests.cs

11
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}

25
src/OpenIddict.Core/OpenIddictCoreHelpers.cs → 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
{
/// <summary>
/// Exposes common helpers used by the OpenIddict assemblies.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class OpenIddictCoreHelpers
internal static class OpenIddictHelpers
{
/// <summary>
/// Finds the base type that matches the specified generic type definition.
/// Finds the first base type that matches the specified generic type definition.
/// </summary>
/// <param name="type">The type to introspect.</param>
/// <param name="definition">The generic type definition.</param>
/// <returns>A <see cref="Type"/> instance if the base type was found, <c>null</c> otherwise.</returns>
public static Type FindGenericBaseType(Type type, Type definition)
=> FindGenericBaseTypes(type, definition).FirstOrDefault();
/// <summary>
/// Finds all the base types that matches the specified generic type definition.
/// </summary>
/// <param name="type">The type to introspect.</param>
/// <param name="definition">The generic type definition.</param>
/// <returns>A <see cref="Type"/> instance if the base type was found, <c>null</c> otherwise.</returns>
public static IEnumerable<Type> 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;
}
}
}

10
shared/OpenIddict.Extensions/OpenIddict.Extensions.csproj

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>

4
src/OpenIddict.Core/OpenIddict.Core.csproj

@ -23,4 +23,8 @@
<PackageReference Include="Microsoft.Extensions.Options" Version="$(AspNetCoreVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
</Project>

17
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));

6
src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\packages.props" />
@ -23,4 +23,8 @@
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
</Project>

4
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()

4
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()

4
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()

4
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()

4
src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj

@ -23,4 +23,8 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="$(AspNetCoreVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
</Project>

4
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()

5
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()

4
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()

4
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()

9
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.
/// </summary>
/// <param name="notification">The event to process.</param>
/// <param name="cancellationToken">
/// The <see cref="CancellationToken"/> that can be used to abort the operation.
/// </param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result determines whether next handlers in the pipeline are invoked.
/// </returns>
Task HandleAsync([NotNull] TEvent notification, CancellationToken cancellationToken);
Task<OpenIddictServerEventState> HandleAsync([NotNull] TEvent notification);
}
}

26
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
{
/// <summary>
/// Dispatches events by invoking the corresponding handlers.
/// </summary>
public interface IOpenIddictServerEventService
{
/// <summary>
/// Publishes a new event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to publish.</typeparam>
/// <param name="notification">The event to publish.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
Task PublishAsync<TEvent>([NotNull] TEvent notification) where TEvent : class, IOpenIddictServerEvent;
}
}

163
src/OpenIddict.Server/Internal/OpenIddictServerEventService.cs

@ -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
{
/// <summary>
/// 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.
/// </summary>
public class OpenIddictServerEventService
{
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictServerEventService"/> 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.
/// </summary>
public OpenIddictServerEventService([NotNull] IServiceProvider provider)
{
_provider = provider;
}
/// <summary>
/// Publishes a new event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to publish.</typeparam>
/// <param name="notification">The event to publish.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public async Task PublishAsync<TEvent>([NotNull] TEvent notification, CancellationToken cancellationToken = default)
where TEvent : class, IOpenIddictServerEvent
{
if (notification == null)
{
throw new ArgumentNullException(nameof(notification));
}
foreach (var handler in _provider.GetServices<IOpenIddictServerEventHandler<TEvent>>())
{
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;
}
}
}
}
}

5
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
/// </summary>
public OpenIddictServerProvider(
[NotNull] ILogger<OpenIddictServerProvider> logger,
[NotNull] OpenIddictServerEventService eventService,
[NotNull] IOpenIddictServerEventService eventService,
[NotNull] IOpenIddictApplicationManager applicationManager,
[NotNull] IOpenIddictAuthorizationManager authorizationManager,
[NotNull] IOpenIddictScopeManager scopeManager,

4
src/OpenIddict.Server/OpenIddict.Server.csproj

@ -25,4 +25,8 @@
<PackageReference Include="Newtonsoft.Json.Bson" Version="$(JsonNetBsonVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
</Project>

67
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; }
/// <summary>
/// Registers an event handler for the specified event type.
/// Registers an inline event handler for the specified event type.
/// </summary>
/// <param name="handler">The handler added to the DI container.</param>
/// <param name="handler">The handler delegate.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictServerBuilder AddEventHandler<TEvent>(
[NotNull] IOpenIddictServerEventHandler<TEvent> handler)
[NotNull] Func<TEvent, Task<OpenIddictServerEventState>> 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<IOpenIddictServerEventHandler<TEvent>>(
new OpenIddictServerEventHandler<TEvent>(handler));
return this;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="handler">The handler added to the DI container.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictServerBuilder AddEventHandler<TEvent>([NotNull] Func<TEvent, Task> handler)
where TEvent : class, IOpenIddictServerEvent
=> AddEventHandler<TEvent>((notification, cancellationToken) => handler(notification));
/// <summary>
/// Registers an event handler for the specified event type.
/// </summary>
/// <param name="handler">The handler added to the DI container.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictServerBuilder AddEventHandler<TEvent>([NotNull] Func<TEvent, CancellationToken, Task> handler)
where TEvent : class, IOpenIddictServerEvent
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
return AddEventHandler(new OpenIddictServerEventHandler<TEvent>(handler));
}
/// <summary>
/// Registers an event handler for the specified event type.
/// </summary>
/// <typeparam name="TEvent">The type of the event.</typeparam>
/// <typeparam name="THandler">The type of the handler.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictServerBuilder AddEventHandler<TEvent, THandler>(
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TEvent : class, IOpenIddictServerEvent
where THandler : IOpenIddictServerEventHandler<TEvent>
=> AddEventHandler<TEvent>(typeof(THandler));
public OpenIddictServerBuilder AddEventHandler<THandler>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
=> AddEventHandler(typeof(THandler), lifetime);
/// <summary>
/// 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.
/// </summary>
/// <param name="type">The type of the handler.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictServerBuilder AddEventHandler<TEvent>(
[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<TEvent>).IsAssignableFrom(type))
if (type.IsGenericTypeDefinition)
{
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Add(new ServiceDescriptor(typeof(IOpenIddictServerEventHandler<TEvent>), 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;
}

29
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<TEvent> : IOpenIddictServerEventHandler<TEvent>
where TEvent : class, IOpenIddictServerEvent
{
private readonly Func<TEvent, CancellationToken, Task> _handler;
private readonly Func<TEvent, Task<OpenIddictServerEventState>> _handler;
/// <summary>
/// Creates a new event using the specified handler delegate.
/// </summary>
/// <param name="handler">The event handler delegate</param>
public OpenIddictServerEventHandler([NotNull] Func<TEvent, CancellationToken, Task> handler)
/// <param name="handler">The event handler delegate.</param>
public OpenIddictServerEventHandler([NotNull] Func<TEvent, Task<OpenIddictServerEventState>> handler)
=> _handler = handler ?? throw new ArgumentNullException(nameof(handler));
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="notification">The event to process.</param>
/// <param name="cancellationToken">
/// The <see cref="CancellationToken"/> that can be used to abort the operation.
/// </param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result determines whether next handlers in the pipeline are invoked.
/// </returns>
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<OpenIddictServerEventState> HandleAsync(TEvent notification)
=> _handler(notification ?? throw new ArgumentNullException(nameof(notification)));
}
}

52
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
{
/// <summary>
/// Dispatches events by invoking the corresponding notification handlers.
/// </summary>
public class OpenIddictServerEventService : IOpenIddictServerEventService
{
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictServerEventService"/> class.
/// </summary>
public OpenIddictServerEventService([NotNull] IServiceProvider provider)
=> _provider = provider;
/// <summary>
/// Publishes a new event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to publish.</typeparam>
/// <param name="notification">The event to publish.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public async Task PublishAsync<TEvent>([NotNull] TEvent notification) where TEvent : class, IOpenIddictServerEvent
{
if (notification == null)
{
throw new ArgumentNullException(nameof(notification));
}
foreach (var handler in _provider.GetServices<IOpenIddictServerEventHandler<TEvent>>())
{
switch (await handler.HandleAsync(notification))
{
case OpenIddictServerEventState.Unhandled: continue;
case OpenIddictServerEventState.Handled: return;
default: throw new InvalidOperationException("The specified event state is not valid.");
}
}
}
}
}

33
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
{
/// <summary>
/// Represents the state of an event triggered by the OpenIddict
/// server components and processed by user-defined handlers.
/// </summary>
public enum OpenIddictServerEventState
{
/// <summary>
/// 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()).
/// </summary>
Unhandled = 0,
/// <summary>
/// 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()).
/// </summary>
Handled = 1
}
}

4
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -42,7 +42,7 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.AddMemoryCache();
builder.Services.AddOptions();
builder.Services.TryAddScoped<OpenIddictServerEventService>();
builder.Services.TryAddScoped<IOpenIddictServerEventService, OpenIddictServerEventService>();
builder.Services.TryAddScoped<OpenIddictServerHandler>();
builder.Services.TryAddScoped(provider =>
{
@ -54,7 +54,7 @@ namespace Microsoft.Extensions.DependencyInjection
return new OpenIddictServerProvider(
provider.GetRequiredService<ILogger<OpenIddictServerProvider>>(),
provider.GetRequiredService<OpenIddictServerEventService>(),
provider.GetRequiredService<IOpenIddictServerEventService>(),
provider.GetService<IOpenIddictApplicationManager>() ?? throw CreateException(),
provider.GetService<IOpenIddictAuthorizationManager>() ?? throw CreateException(),
provider.GetService<IOpenIddictScopeManager>() ?? throw CreateException(),

9
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.
/// </summary>
/// <param name="notification">The event to process.</param>
/// <param name="cancellationToken">
/// The <see cref="CancellationToken"/> that can be used to abort the operation.
/// </param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result determines whether next handlers in the pipeline are invoked.
/// </returns>
Task HandleAsync([NotNull] TEvent notification, CancellationToken cancellationToken);
Task<OpenIddictValidationEventState> HandleAsync([NotNull] TEvent notification);
}
}

25
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
{
/// <summary>
/// Dispatches events by invoking the corresponding handlers.
/// </summary>
public interface IOpenIddictValidationEventService
{
/// <summary>
/// Publishes a new event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to publish.</typeparam>
/// <param name="notification">The event to publish.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
Task PublishAsync<TEvent>([NotNull] TEvent notification) where TEvent : class, IOpenIddictValidationEvent;
}
}

76
src/OpenIddict.Validation/Internal/OpenIddictValidationEventService.cs

@ -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
{
/// <summary>
/// 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.
/// </summary>
public class OpenIddictValidationEventService
{
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationEventService"/> 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.
/// </summary>
public OpenIddictValidationEventService([NotNull] IServiceProvider provider)
{
_provider = provider;
}
/// <summary>
/// Publishes a new event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to publish.</typeparam>
/// <param name="notification">The event to publish.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public async Task PublishAsync<TEvent>([NotNull] TEvent notification, CancellationToken cancellationToken = default)
where TEvent : class, IOpenIddictValidationEvent
{
if (notification == null)
{
throw new ArgumentNullException(nameof(notification));
}
foreach (var handler in _provider.GetServices<IOpenIddictValidationEventHandler<TEvent>>())
{
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;
}
}
}
}
}

4
src/OpenIddict.Validation/Internal/OpenIddictValidationProvider.cs

@ -21,14 +21,14 @@ namespace OpenIddict.Validation.Internal
/// </summary>
public sealed class OpenIddictValidationProvider : OAuthValidationEvents
{
private readonly OpenIddictValidationEventService _eventService;
private readonly IOpenIddictValidationEventService _eventService;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationProvider"/> 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.
/// </summary>
public OpenIddictValidationProvider([NotNull] OpenIddictValidationEventService eventService)
public OpenIddictValidationProvider([NotNull] IOpenIddictValidationEventService eventService)
=> _eventService = eventService;
public override Task ApplyChallenge([NotNull] ApplyChallengeContext context)

4
src/OpenIddict.Validation/OpenIddict.Validation.csproj

@ -22,4 +22,8 @@
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(AspNetCoreVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
</Project>

68
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; }
/// <summary>
/// Registers an event handler for the specified event type.
/// Registers an inline event handler for the specified event type.
/// </summary>
/// <param name="handler">The handler added to the DI container.</param>
/// <param name="handler">The handler delegate.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictValidationBuilder AddEventHandler<TEvent>(
[NotNull] IOpenIddictValidationEventHandler<TEvent> handler)
[NotNull] Func<TEvent, Task<OpenIddictValidationEventState>> 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<IOpenIddictValidationEventHandler<TEvent>>(
new OpenIddictValidationEventHandler<TEvent>(handler));
return this;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="handler">The handler added to the DI container.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictValidationBuilder AddEventHandler<TEvent>([NotNull] Func<TEvent, Task> handler)
where TEvent : class, IOpenIddictValidationEvent
=> AddEventHandler<TEvent>((notification, cancellationToken) => handler(notification));
/// <summary>
/// Registers an event handler for the specified event type.
/// </summary>
/// <param name="handler">The handler added to the DI container.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictValidationBuilder AddEventHandler<TEvent>([NotNull] Func<TEvent, CancellationToken, Task> handler)
where TEvent : class, IOpenIddictValidationEvent
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
return AddEventHandler(new OpenIddictValidationEventHandler<TEvent>(handler));
}
/// <summary>
/// Registers an event handler for the specified event type.
/// </summary>
/// <typeparam name="TEvent">The type of the event.</typeparam>
/// <typeparam name="THandler">The type of the handler.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictValidationBuilder AddEventHandler<TEvent, THandler>(
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TEvent : class, IOpenIddictValidationEvent
where THandler : IOpenIddictValidationEventHandler<TEvent>
=> AddEventHandler<TEvent>(typeof(THandler));
public OpenIddictValidationBuilder AddEventHandler<THandler>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
=> AddEventHandler(typeof(THandler), lifetime);
/// <summary>
/// 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.
/// </summary>
/// <param name="type">The type of the handler.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public OpenIddictValidationBuilder AddEventHandler<TEvent>(
[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<TEvent>).IsAssignableFrom(type))
if (type.IsGenericTypeDefinition)
{
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Add(new ServiceDescriptor(typeof(IOpenIddictValidationEventHandler<TEvent>), 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;
}

29
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<TEvent> : IOpenIddictValidationEventHandler<TEvent>
where TEvent : class, IOpenIddictValidationEvent
{
private readonly Func<TEvent, CancellationToken, Task> _handler;
private readonly Func<TEvent, Task<OpenIddictValidationEventState>> _handler;
/// <summary>
/// Creates a new event using the specified handler delegate.
/// </summary>
/// <param name="handler">The event handler delegate</param>
public OpenIddictValidationEventHandler([NotNull] Func<TEvent, CancellationToken, Task> handler)
/// <param name="handler">The event handler delegate.</param>
public OpenIddictValidationEventHandler([NotNull] Func<TEvent, Task<OpenIddictValidationEventState>> handler)
=> _handler = handler ?? throw new ArgumentNullException(nameof(handler));
/// <summary>
/// Processes the event.
/// </summary>
/// <param name="notification">The event to process.</param>
/// <param name="cancellationToken">
/// The <see cref="CancellationToken"/> that can be used to abort the operation.
/// </param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result determines whether next handlers in the pipeline are invoked.
/// </returns>
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<OpenIddictValidationEventState> HandleAsync(TEvent notification)
=> _handler(notification ?? throw new ArgumentNullException(nameof(notification)));
}
}

52
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
{
/// <summary>
/// Dispatches events by invoking the corresponding notification handlers.
/// </summary>
public class OpenIddictValidationEventService : IOpenIddictValidationEventService
{
private readonly IServiceProvider _provider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictValidationEventService"/> class.
/// </summary>
public OpenIddictValidationEventService([NotNull] IServiceProvider provider)
=> _provider = provider;
/// <summary>
/// Publishes a new event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to publish.</typeparam>
/// <param name="notification">The event to publish.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public async Task PublishAsync<TEvent>([NotNull] TEvent notification) where TEvent : class, IOpenIddictValidationEvent
{
if (notification == null)
{
throw new ArgumentNullException(nameof(notification));
}
foreach (var handler in _provider.GetServices<IOpenIddictValidationEventHandler<TEvent>>())
{
switch (await handler.HandleAsync(notification))
{
case OpenIddictValidationEventState.Unhandled: continue;
case OpenIddictValidationEventState.Handled: return;
default: throw new InvalidOperationException("The specified event state is not valid.");
}
}
}
}
}

33
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
{
/// <summary>
/// Represents the state of an event triggered by the OpenIddict
/// validation components and processed by user-defined handlers.
/// </summary>
public enum OpenIddictValidationEventState
{
/// <summary>
/// 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()).
/// </summary>
Unhandled = 0,
/// <summary>
/// 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()).
/// </summary>
Handled = 1
}
}

2
src/OpenIddict.Validation/OpenIddictValidationExtensions.cs

@ -41,7 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.AddLogging();
builder.Services.AddOptions();
builder.Services.TryAddScoped<OpenIddictValidationEventService>();
builder.Services.TryAddScoped<IOpenIddictValidationEventService, OpenIddictValidationEventService>();
builder.Services.TryAddScoped<OpenIddictValidationHandler>();
builder.Services.TryAddScoped<OpenIddictValidationProvider>();

64
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<ApplyAuthorizationResponse>(
(notification, cancellationToken) => Task.CompletedTask);
// Act
builder.AddEventHandler(handler);
builder.AddEventHandler<ApplyAuthorizationResponse>(notification => Task.FromResult(OpenIddictServerEventState.Handled));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(IOpenIddictServerEventHandler<ApplyAuthorizationResponse>) &&
service.ImplementationInstance == handler);
service.ImplementationInstance.GetType() == typeof(OpenIddictServerEventHandler<ApplyAuthorizationResponse>));
}
[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<ArgumentException>(delegate
{
return builder.AddEventHandler<ApplyAuthorizationResponse>(typeof(object));
return builder.AddEventHandler<CustomHandler>(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<ArgumentException>(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<ArgumentException>(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<ApplyAuthorizationResponse, CustomHandler>();
builder.AddEventHandler<CustomHandler>(ServiceLifetime.Singleton);
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(IOpenIddictServerEventHandler<ApplyAuthorizationResponse>) &&
service.ImplementationType == typeof(CustomHandler));
service.ImplementationType == typeof(CustomHandler) &&
service.Lifetime == ServiceLifetime.Singleton);
Assert.Contains(services, service =>
service.ServiceType == typeof(IOpenIddictServerEventHandler<HandleAuthorizationRequest>) &&
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<ApplyAuthorizationResponse>
public class CustomHandler : IOpenIddictServerEventHandler<ApplyAuthorizationResponse>,
IOpenIddictServerEventHandler<HandleAuthorizationRequest>
{
public CustomHandler(Func<ApplyAuthorizationResponse, CancellationToken, Task> handler) : base(handler)
public Task<OpenIddictServerEventState> HandleAsync(ApplyAuthorizationResponse notification)
{
throw new NotImplementedException();
}
public Task<OpenIddictServerEventState> HandleAsync(HandleAuthorizationRequest notification)
{
throw new NotImplementedException();
}
}
}

60
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<ArgumentNullException>(()
=> new OpenIddictServerEventHandler<Event>(handler: null));
Assert.Equal("handler", exception.ParamName);
}
[Fact]
public async Task HandleAsync_ThrowsAnExceptionForNullNotification()
{
// Arrange
var handler = new OpenIddictServerEventHandler<Event>(
notification => Task.FromResult(OpenIddictServerEventState.Handled));
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(()
=> handler.HandleAsync(notification: null));
Assert.Equal("notification", exception.ParamName);
}
[Fact]
public async Task HandleAsync_InvokesInlineHandler()
{
// Arrange
var marker = false;
var handler = new OpenIddictServerEventHandler<Event>(
notification =>
{
marker = true;
return Task.FromResult(OpenIddictServerEventState.Handled);
});
// Act
await handler.HandleAsync(new Event());
// Assert
Assert.True(marker);
}
public class Event : IOpenIddictServerEvent { }
}
}

92
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<IServiceProvider>();
var service = new OpenIddictServerEventService(provider);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(()
=> service.PublishAsync<Event>(notification: null));
Assert.Equal("notification", exception.ParamName);
}
[Fact]
public async Task PublishAsync_InvokesHandlers()
{
// Arrange
var handlers = new List<IOpenIddictServerEventHandler<Event>>
{
Mock.Of<IOpenIddictServerEventHandler<Event>>(),
Mock.Of<IOpenIddictServerEventHandler<Event>>()
};
var provider = new Mock<IServiceProvider>();
provider.Setup(mock => mock.GetService(typeof(IEnumerable<IOpenIddictServerEventHandler<Event>>)))
.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<IOpenIddictServerEventHandler<Event>>
{
Mock.Of<IOpenIddictServerEventHandler<Event>>(
mock => mock.HandleAsync(It.IsAny<Event>()) == Task.FromResult(OpenIddictServerEventState.Unhandled)),
Mock.Of<IOpenIddictServerEventHandler<Event>>(
mock => mock.HandleAsync(It.IsAny<Event>()) == Task.FromResult(OpenIddictServerEventState.Unhandled)),
Mock.Of<IOpenIddictServerEventHandler<Event>>(
mock => mock.HandleAsync(It.IsAny<Event>()) == Task.FromResult(OpenIddictServerEventState.Handled)),
Mock.Of<IOpenIddictServerEventHandler<Event>>()
};
var provider = new Mock<IServiceProvider>();
provider.Setup(mock => mock.GetService(typeof(IEnumerable<IOpenIddictServerEventHandler<Event>>)))
.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 { }
}
}

2
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));
}

64
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<CreateTicket>(
(notification, cancellationToken) => Task.CompletedTask);
// Act
builder.AddEventHandler(handler);
builder.AddEventHandler<CreateTicket>(notification => Task.FromResult(OpenIddictValidationEventState.Handled));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(IOpenIddictValidationEventHandler<CreateTicket>) &&
service.ImplementationInstance == handler);
service.ImplementationInstance.GetType() == typeof(OpenIddictValidationEventHandler<CreateTicket>));
}
[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<ArgumentException>(delegate
{
return builder.AddEventHandler<CreateTicket>(typeof(object));
return builder.AddEventHandler<CustomHandler>(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<ArgumentException>(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<ArgumentException>(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<CreateTicket, CustomHandler>();
builder.AddEventHandler<CustomHandler>(ServiceLifetime.Singleton);
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(IOpenIddictValidationEventHandler<ApplyChallenge>) &&
service.ImplementationType == typeof(CustomHandler) &&
service.Lifetime == ServiceLifetime.Singleton);
Assert.Contains(services, service =>
service.ServiceType == typeof(IOpenIddictValidationEventHandler<CreateTicket>) &&
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<CreateTicket>
public class CustomHandler : IOpenIddictValidationEventHandler<ApplyChallenge>,
IOpenIddictValidationEventHandler<CreateTicket>
{
public CustomHandler(Func<CreateTicket, CancellationToken, Task> handler) : base(handler)
public Task<OpenIddictValidationEventState> HandleAsync(ApplyChallenge notification)
{
throw new NotImplementedException();
}
public Task<OpenIddictValidationEventState> HandleAsync(CreateTicket notification)
{
throw new NotImplementedException();
}
}
}

60
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<ArgumentNullException>(()
=> new OpenIddictValidationEventHandler<Event>(handler: null));
Assert.Equal("handler", exception.ParamName);
}
[Fact]
public async Task HandleAsync_ThrowsAnExceptionForNullNotification()
{
// Arrange
var handler = new OpenIddictValidationEventHandler<Event>(
notification => Task.FromResult(OpenIddictValidationEventState.Handled));
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(()
=> handler.HandleAsync(notification: null));
Assert.Equal("notification", exception.ParamName);
}
[Fact]
public async Task HandleAsync_InvokesInlineHandler()
{
// Arrange
var marker = false;
var handler = new OpenIddictValidationEventHandler<Event>(
notification =>
{
marker = true;
return Task.FromResult(OpenIddictValidationEventState.Handled);
});
// Act
await handler.HandleAsync(new Event());
// Assert
Assert.True(marker);
}
public class Event : IOpenIddictValidationEvent { }
}
}

92
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<IServiceProvider>();
var service = new OpenIddictValidationEventService(provider);
// Act and assert
var exception = await Assert.ThrowsAsync<ArgumentNullException>(()
=> service.PublishAsync<Event>(notification: null));
Assert.Equal("notification", exception.ParamName);
}
[Fact]
public async Task PublishAsync_InvokesHandlers()
{
// Arrange
var handlers = new List<IOpenIddictValidationEventHandler<Event>>
{
Mock.Of<IOpenIddictValidationEventHandler<Event>>(),
Mock.Of<IOpenIddictValidationEventHandler<Event>>()
};
var provider = new Mock<IServiceProvider>();
provider.Setup(mock => mock.GetService(typeof(IEnumerable<IOpenIddictValidationEventHandler<Event>>)))
.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<IOpenIddictValidationEventHandler<Event>>
{
Mock.Of<IOpenIddictValidationEventHandler<Event>>(
mock => mock.HandleAsync(It.IsAny<Event>()) == Task.FromResult(OpenIddictValidationEventState.Unhandled)),
Mock.Of<IOpenIddictValidationEventHandler<Event>>(
mock => mock.HandleAsync(It.IsAny<Event>()) == Task.FromResult(OpenIddictValidationEventState.Unhandled)),
Mock.Of<IOpenIddictValidationEventHandler<Event>>(
mock => mock.HandleAsync(It.IsAny<Event>()) == Task.FromResult(OpenIddictValidationEventState.Handled)),
Mock.Of<IOpenIddictValidationEventHandler<Event>>()
};
var provider = new Mock<IServiceProvider>();
provider.Setup(mock => mock.GetService(typeof(IEnumerable<IOpenIddictValidationEventHandler<Event>>)))
.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 { }
}
}

2
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));
}

Loading…
Cancel
Save