Browse Source

Remove all the store resolvers and mark all the assemblies as trimming/Native AOT-compatible

pull/2280/head
Kévin Chalet 1 year ago
parent
commit
5b56eddc0c
  1. 10
      Directory.Build.targets
  2. 129
      shared/OpenIddict.Extensions/OpenIddictHelpers.cs
  3. 102
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  4. 14
      src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
  5. 55
      src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
  6. 32
      src/OpenIddict.Abstractions/Primitives/OpenIddictSerializer.cs
  7. 15
      src/OpenIddict.Abstractions/Resolvers/IOpenIddictApplicationStoreResolver.cs
  8. 15
      src/OpenIddict.Abstractions/Resolvers/IOpenIddictAuthorizationStoreResolver.cs
  9. 15
      src/OpenIddict.Abstractions/Resolvers/IOpenIddictScopeStoreResolver.cs
  10. 15
      src/OpenIddict.Abstractions/Resolvers/IOpenIddictTokenStoreResolver.cs
  11. 4
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs
  12. 4
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs
  13. 4
      src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
  14. 4
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs
  15. 6
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
  16. 53
      src/OpenIddict.Client/OpenIddictClientConfiguration.cs
  17. 98
      src/OpenIddict.Client/OpenIddictClientHandlerDescriptor.cs
  18. 12
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  19. 9
      src/OpenIddict.Core/Caches/OpenIddictApplicationCache.cs
  20. 9
      src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs
  21. 9
      src/OpenIddict.Core/Caches/OpenIddictScopeCache.cs
  22. 9
      src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs
  23. 14
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  24. 14
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  25. 14
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  26. 14
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  27. 678
      src/OpenIddict.Core/OpenIddictCoreBuilder.cs
  28. 66
      src/OpenIddict.Core/OpenIddictCoreExtensions.cs
  29. 26
      src/OpenIddict.Core/OpenIddictCoreOptions.cs
  30. 24
      src/OpenIddict.Core/Resolvers/OpenIddictApplicationStoreResolver.cs
  31. 24
      src/OpenIddict.Core/Resolvers/OpenIddictAuthorizationStoreResolver.cs
  32. 24
      src/OpenIddict.Core/Resolvers/OpenIddictScopeStoreResolver.cs
  33. 24
      src/OpenIddict.Core/Resolvers/OpenIddictTokenStoreResolver.cs
  34. 7
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkApplicationConfiguration.cs
  35. 7
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkAuthorizationConfiguration.cs
  36. 5
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkScopeConfiguration.cs
  37. 7
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkTokenConfiguration.cs
  38. 25
      src/OpenIddict.EntityFramework/IOpenIddictEntityFrameworkContext.cs
  39. 63
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs
  40. 46
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkContext.cs
  41. 22
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs
  42. 8
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs
  43. 6
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkOptions.cs
  44. 72
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolver.cs
  45. 72
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolver.cs
  46. 70
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkScopeStoreResolver.cs
  47. 72
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkTokenStoreResolver.cs
  48. 155
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs
  49. 223
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs
  50. 145
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs
  51. 278
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs
  52. 7
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreApplicationConfiguration.cs
  53. 7
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreAuthorizationConfiguration.cs
  54. 5
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreScopeConfiguration.cs
  55. 7
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreTokenConfiguration.cs
  56. 25
      src/OpenIddict.EntityFrameworkCore/IOpenIddictEntityFrameworkCoreContext.cs
  57. 60
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs
  58. 46
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreContext.cs
  59. 8
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs
  60. 22
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs
  61. 44
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
  62. 7
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs
  63. 72
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolver.cs
  64. 72
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.cs
  65. 70
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolver.cs
  66. 72
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolver.cs
  67. 182
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
  68. 285
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
  69. 149
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs
  70. 358
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
  71. 38
      src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs
  72. 13
      src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs
  73. 50
      src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbApplicationStoreResolver.cs
  74. 50
      src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbAuthorizationStoreResolver.cs
  75. 50
      src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbScopeStoreResolver.cs
  76. 50
      src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbTokenStoreResolver.cs
  77. 19
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs
  78. 17
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
  79. 17
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs
  80. 17
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs
  81. 4
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  82. 4
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
  83. 98
      src/OpenIddict.Server/OpenIddictServerHandlerDescriptor.cs
  84. 16
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  85. 4
      src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
  86. 98
      src/OpenIddict.Validation/OpenIddictValidationHandlerDescriptor.cs
  87. 469
      test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs
  88. 120
      test/OpenIddict.Core.Tests/OpenIddictCoreExtensionsTests.cs
  89. 44
      test/OpenIddict.Core.Tests/Resolvers/OpenIddictApplicationStoreResolverTests.cs
  90. 44
      test/OpenIddict.Core.Tests/Resolvers/OpenIddictAuthorizationStoreResolverTests.cs
  91. 44
      test/OpenIddict.Core.Tests/Resolvers/OpenIddictScopeStoreResolverTests.cs
  92. 44
      test/OpenIddict.Core.Tests/Resolvers/OpenIddictTokenStoreResolverTests.cs
  93. 86
      test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkBuilderTests.cs
  94. 90
      test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs
  95. 105
      test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolverTests.cs
  96. 105
      test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolverTests.cs
  97. 105
      test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkScopeStoreResolverTests.cs
  98. 105
      test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkTokenStoreResolverTests.cs
  99. 94
      test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreBuilderTests.cs
  100. 90
      test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs

10
Directory.Build.targets

@ -15,6 +15,15 @@
<PublicSign>false</PublicSign>
</PropertyGroup>
<!--
Note: the trimming and Native AOT analyzers are only supported on .NET.
-->
<PropertyGroup
Condition=" $(RepoRelativeProjectDir.Contains('src')) And '$(TargetFrameworkIdentifier)' == '.NETCoreApp' ">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<!--
Note: .NET Native, .NET Framework and .NET Standard assemblies are not annotated with
nullable references annotations. To avoid errors on these target frameworks, related
@ -55,6 +64,7 @@
<DefineConstants>$(DefineConstants);SUPPORTS_INT32_RANDOM_NUMBER_GENERATOR_METHODS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_MULTIPLE_VALUES_IN_QUERYHELPERS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_NAMED_PIPE_STATIC_FACTORY_WITH_ACL</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_NATIVE_AOT</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ONE_SHOT_HASHING_METHODS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_ONE_SHOT_RANDOM_NUMBER_GENERATOR_METHODS</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_OPERATING_SYSTEM_VERSIONS_COMPARISON</DefineConstants>

129
shared/OpenIddict.Extensions/OpenIddictHelpers.cs

@ -52,71 +52,6 @@ internal static class OpenIddictHelpers
}
}
/// <summary>
/// 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, <see langword="null"/> 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, <see langword="null"/> otherwise.</returns>
public static IEnumerable<Type> FindGenericBaseTypes(Type type, Type definition)
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (definition is null)
{
throw new ArgumentNullException(nameof(definition));
}
if (!definition.IsGenericTypeDefinition)
{
throw new ArgumentException(SR.GetResourceString(SR.ID0263), nameof(definition));
}
if (definition.IsInterface)
{
foreach (var contract in type.GetInterfaces())
{
if (!contract.IsGenericType && !contract.IsConstructedGenericType)
{
continue;
}
if (contract.GetGenericTypeDefinition() == definition)
{
yield return contract;
}
}
}
else
{
for (var candidate = type; candidate is not null; candidate = candidate.BaseType)
{
if (!candidate.IsGenericType && !candidate.IsConstructedGenericType)
{
continue;
}
if (candidate.GetGenericTypeDefinition() == definition)
{
yield return candidate;
}
}
}
}
#if !SUPPORTS_TASK_WAIT_ASYNC
/// <summary>
/// Waits until the specified task returns a result or the cancellation token is signaled.
@ -502,13 +437,19 @@ internal static class OpenIddictHelpers
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
public static ECDsa CreateEcdsaKey()
=> CryptoConfig.CreateFromName("OpenIddict ECDSA Cryptographic Provider") switch
{
return GetAlgorithmFromConfig() switch
{
ECDsa result => result,
null => ECDsa.Create(),
var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName))
};
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig() => CryptoConfig.CreateFromName("OpenIddict ECDSA Cryptographic Provider");
}
/// <summary>
/// Creates a new <see cref="ECDsa"/> key.
/// </summary>
@ -519,7 +460,7 @@ internal static class OpenIddictHelpers
/// </exception>
public static ECDsa CreateEcdsaKey(ECCurve curve)
{
var algorithm = CryptoConfig.CreateFromName("OpenIddict ECDSA Cryptographic Provider") switch
var algorithm = GetAlgorithmFromConfig() switch
{
ECDsa result => result,
null => null,
@ -546,6 +487,10 @@ internal static class OpenIddictHelpers
}
return algorithm;
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig() => CryptoConfig.CreateFromName("OpenIddict ECDSA Cryptographic Provider");
}
#endif
@ -559,7 +504,7 @@ internal static class OpenIddictHelpers
/// </exception>
public static RSA CreateRsaKey(int size)
{
var algorithm = CryptoConfig.CreateFromName("OpenIddict RSA Cryptographic Provider") switch
var algorithm = GetAlgorithmFromConfig() switch
{
RSA result => result,
@ -619,6 +564,10 @@ internal static class OpenIddictHelpers
}
return algorithm;
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig() => CryptoConfig.CreateFromName("OpenIddict RSA Cryptographic Provider");
}
/// <summary>
@ -632,7 +581,7 @@ internal static class OpenIddictHelpers
/// </exception>
public static byte[] ComputeSha256MessageAuthenticationCode(byte[] key, byte[] data)
{
var algorithm = CryptoConfig.CreateFromName("OpenIddict HMAC SHA-256 Cryptographic Provider", [key]) switch
var algorithm = GetAlgorithmFromConfig(key) switch
{
HMACSHA256 result => result,
null => null,
@ -659,6 +608,10 @@ internal static class OpenIddictHelpers
{
algorithm.Dispose();
}
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig(byte[] key) => CryptoConfig.CreateFromName("OpenIddict HMAC SHA-256 Cryptographic Provider", [key]);
}
/// <summary>
@ -671,7 +624,7 @@ internal static class OpenIddictHelpers
/// </exception>
public static byte[] ComputeSha256Hash(byte[] data)
{
var algorithm = CryptoConfig.CreateFromName("OpenIddict SHA-256 Cryptographic Provider") switch
var algorithm = GetAlgorithmFromConfig() switch
{
SHA256 result => result,
null => null,
@ -698,6 +651,10 @@ internal static class OpenIddictHelpers
{
algorithm.Dispose();
}
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig() => CryptoConfig.CreateFromName("OpenIddict SHA-256 Cryptographic Provider");
}
/// <summary>
@ -710,7 +667,7 @@ internal static class OpenIddictHelpers
/// </exception>
public static byte[] ComputeSha384Hash(byte[] data)
{
var algorithm = CryptoConfig.CreateFromName("OpenIddict SHA-384 Cryptographic Provider") switch
var algorithm = GetAlgorithmFromConfig() switch
{
SHA384 result => result,
null => null,
@ -737,6 +694,10 @@ internal static class OpenIddictHelpers
{
algorithm.Dispose();
}
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig() => CryptoConfig.CreateFromName("OpenIddict SHA-384 Cryptographic Provider");
}
/// <summary>
@ -749,7 +710,7 @@ internal static class OpenIddictHelpers
/// </exception>
public static byte[] ComputeSha512Hash(byte[] data)
{
var algorithm = CryptoConfig.CreateFromName("OpenIddict SHA-512 Cryptographic Provider") switch
var algorithm = GetAlgorithmFromConfig() switch
{
SHA512 result => result,
null => null,
@ -776,6 +737,10 @@ internal static class OpenIddictHelpers
{
algorithm.Dispose();
}
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig() => CryptoConfig.CreateFromName("OpenIddict SHA-512 Cryptographic Provider");
}
/// <summary>
@ -788,7 +753,7 @@ internal static class OpenIddictHelpers
/// </exception>
public static byte[] CreateRandomArray(int size)
{
var algorithm = CryptoConfig.CreateFromName("OpenIddict RNG Cryptographic Provider") switch
var algorithm = GetAlgorithmFromConfig() switch
{
RandomNumberGenerator result => result,
null => null,
@ -825,6 +790,10 @@ internal static class OpenIddictHelpers
}
return array;
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig() => CryptoConfig.CreateFromName("OpenIddict RNG Cryptographic Provider");
}
/// <summary>
@ -839,7 +808,7 @@ internal static class OpenIddictHelpers
/// </exception>
public static string CreateRandomString(ReadOnlySpan<string> charset, int count)
{
var algorithm = CryptoConfig.CreateFromName("OpenIddict RNG Cryptographic Provider") switch
var algorithm = GetAlgorithmFromConfig() switch
{
RandomNumberGenerator result => result,
null => null,
@ -908,6 +877,10 @@ internal static class OpenIddictHelpers
return (int) value + range.Start.Value;
}
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig() => CryptoConfig.CreateFromName("OpenIddict RNG Cryptographic Provider");
}
/// <summary>
@ -1036,8 +1009,7 @@ internal static class OpenIddictHelpers
{
// Warning: the type and order of the arguments specified here MUST exactly match the parameters used with
// Rfc2898DeriveBytes(string password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm).
using var generator = CryptoConfig.CreateFromName("OpenIddict PBKDF2 Cryptographic Provider",
args: [secret, salt, iterations, algorithm]) switch
using var generator = GetAlgorithmFromConfig(secret, salt, iterations, algorithm) switch
{
Rfc2898DeriveBytes result => result,
@ -1049,6 +1021,11 @@ internal static class OpenIddictHelpers
};
return generator.GetBytes(length);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static object? GetAlgorithmFromConfig(string secret, byte[] salt, int iterations, HashAlgorithmName algorithm)
=> CryptoConfig.CreateFromName("OpenIddict PBKDF2 Cryptographic Provider", [secret, salt, iterations, algorithm]);
}
#endif

102
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -881,51 +881,15 @@ To register the validation services, use 'services.AddOpenIddict().AddValidation
<data name="ID0227" xml:space="preserve">
<value>One or more validation error(s) occurred while trying to update an existing token:</value>
</data>
<data name="ID0228" xml:space="preserve">
<value>No application store has been registered in the dependency injection container.
To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' package and call 'services.AddOpenIddict().AddCore().UseEntityFrameworkCore()'.
To register a custom store, create an implementation of 'IOpenIddictApplicationStore' and use 'services.AddOpenIddict().AddCore().AddApplicationStore()' to add it to the DI container.</value>
</data>
<data name="ID0229" xml:space="preserve">
<value>No authorization store has been registered in the dependency injection container.
To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' package and call 'services.AddOpenIddict().AddCore().UseEntityFrameworkCore()'.
To register a custom store, create an implementation of 'IOpenIddictAuthorizationStore' and use 'services.AddOpenIddict().AddCore().AddAuthorizationStore()' to add it to the DI container.</value>
</data>
<data name="ID0230" xml:space="preserve">
<value>No scope store has been registered in the dependency injection container.
To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' package and call 'services.AddOpenIddict().AddCore().UseEntityFrameworkCore()'.
To register a custom store, create an implementation of 'IOpenIddictScopeStore' and use 'services.AddOpenIddict().AddCore().AddScopeStore()' to add it to the DI container.</value>
</data>
<data name="ID0231" xml:space="preserve">
<value>No token store has been registered in the dependency injection container.
To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' package and call 'services.AddOpenIddict().AddCore().UseEntityFrameworkCore()'.
To register a custom store, create an implementation of 'IOpenIddictTokenStore' and use 'services.AddOpenIddict().AddCore().AddTokenStore()' to add it to the DI container.</value>
</data>
<data name="ID0232" xml:space="preserve">
<value>The specified type is invalid.</value>
</data>
<data name="ID0233" xml:space="preserve">
<value>The cache size cannot be less than 10.</value>
</data>
<data name="ID0234" xml:space="preserve">
<value>The specified application type is not compatible with the Entity Framework 6.x stores.
When enabling the Entity Framework 6.x stores, make sure you use the built-in 'OpenIddictEntityFrameworkApplication' entity or a custom entity that inherits from the generic 'OpenIddictEntityFrameworkApplication' entity.</value>
</data>
<data name="ID0235" xml:space="preserve">
<value>No Entity Framework 6.x context was specified in the OpenIddict options.
<value>No Entity Framework 6.x context was configured to be used with OpenIddict.
To configure the OpenIddict Entity Framework 6.x stores to use a specific 'DbContext', use 'options.UseEntityFramework().UseDbContext&lt;TContext&gt;()'.</value>
</data>
<data name="ID0236" xml:space="preserve">
<value>The specified authorization type is not compatible with the Entity Framework 6.x stores.
When enabling the Entity Framework 6.x stores, make sure you use the built-in 'OpenIddictEntityFrameworkAuthorization' entity or a custom entity that inherits from the generic 'OpenIddictEntityFrameworkAuthorization' entity.</value>
</data>
<data name="ID0237" xml:space="preserve">
<value>The specified scope type is not compatible with the Entity Framework 6.x stores.
When enabling the Entity Framework 6.x stores, make sure you use the built-in 'OpenIddictEntityFrameworkScope' entity or a custom entity that inherits from the generic 'OpenIddictEntityFrameworkScope' entity.</value>
</data>
<data name="ID0238" xml:space="preserve">
<value>The specified token type is not compatible with the Entity Framework 6.x stores.
When enabling the Entity Framework 6.x stores, make sure you use the built-in 'OpenIddictEntityFrameworkToken' entity or a custom entity that inherits from the generic 'OpenIddictEntityFrameworkToken' entity.</value>
</data>
<data name="ID0239" xml:space="preserve">
<value>The application was concurrently updated and cannot be persisted in its current state.
@ -974,41 +938,9 @@ Make sure that the token entity is not abstract and has a public parameterless c
<data name="ID0251" xml:space="preserve">
<value>The authorization associated with the token cannot be found.</value>
</data>
<data name="ID0252" xml:space="preserve">
<value>The specified application type is not compatible with the Entity Framework Core stores.
When enabling the Entity Framework Core stores, make sure you use the built-in 'OpenIddictEntityFrameworkCoreApplication' entity or a custom entity that inherits from the generic 'OpenIddictEntityFrameworkCoreApplication' entity.</value>
</data>
<data name="ID0253" xml:space="preserve">
<value>No Entity Framework Core context was specified in the OpenIddict options.
<value>No Entity Framework Core context was configured to be used with OpenIddict.
To configure the OpenIddict Entity Framework Core stores to use a specific 'DbContext', use 'options.UseEntityFrameworkCore().UseDbContext&lt;TContext&gt;()'.</value>
</data>
<data name="ID0254" xml:space="preserve">
<value>The specified authorization type is not compatible with the Entity Framework Core stores.
When enabling the Entity Framework Core stores, make sure you use the built-in 'OpenIddictEntityFrameworkCoreAuthorization' entity or a custom entity that inherits from the generic 'OpenIddictEntityFrameworkCoreAuthorization' entity.</value>
</data>
<data name="ID0255" xml:space="preserve">
<value>The specified scope type is not compatible with the Entity Framework Core stores.
When enabling the Entity Framework Core stores, make sure you use the built-in 'OpenIddictEntityFrameworkCoreScope' entity or a custom entity that inherits from the generic 'OpenIddictEntityFrameworkCoreScope' entity.</value>
</data>
<data name="ID0256" xml:space="preserve">
<value>The specified token type is not compatible with the Entity Framework Core stores.
When enabling the Entity Framework Core stores, make sure you use the built-in 'OpenIddictEntityFrameworkCoreToken' entity or a custom entity that inherits from the generic 'OpenIddictEntityFrameworkCoreToken' entity.</value>
</data>
<data name="ID0257" xml:space="preserve">
<value>The specified application type is not compatible with the MongoDB stores.
When enabling the MongoDB stores, make sure you use the built-in 'OpenIddictMongoDbApplication' entity or a custom entity that inherits from the 'OpenIddictMongoDbApplication' entity.</value>
</data>
<data name="ID0258" xml:space="preserve">
<value>The specified authorization type is not compatible with the MongoDB stores.
When enabling the MongoDB stores, make sure you use the built-in 'OpenIddictMongoDbAuthorization' entity or a custom entity that inherits from the 'OpenIddictMongoDbAuthorization' entity.</value>
</data>
<data name="ID0259" xml:space="preserve">
<value>The specified scope type is not compatible with the MongoDB stores.
When enabling the MongoDB stores, make sure you use the built-in 'OpenIddictMongoDbScope' entity or a custom entity that inherits from the 'OpenIddictMongoDbScope' entity.</value>
</data>
<data name="ID0260" xml:space="preserve">
<value>The specified token type is not compatible with the MongoDB stores.
When enabling the MongoDB stores, make sure you use the built-in 'OpenIddictMongoDbToken' entity or a custom entity that inherits from the 'OpenIddictMongoDbToken' entity.</value>
</data>
<data name="ID0261" xml:space="preserve">
<value>The collection name cannot be null or empty.</value>
@ -1047,22 +979,6 @@ To configure the OpenIddict MongoDB stores to use a specific database, use 'serv
<data name="ID0272" xml:space="preserve">
<value>The configuration response was not correctly applied.
To apply configuration responses, create a class implementing 'IOpenIddictServerHandler&lt;ApplyConfigurationResponseContext&gt;' and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.</value>
</data>
<data name="ID0273" xml:space="preserve">
<value>No default application entity type was configured in the OpenIddict core options, which generally indicates that no application store was registered in the DI container.
To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' package and call 'services.AddOpenIddict().AddCore().UseEntityFrameworkCore()'.</value>
</data>
<data name="ID0274" xml:space="preserve">
<value>No default authorization entity type was configured in the OpenIddict core options, which generally indicates that no authorization store was registered in the DI container.
To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' package and call 'services.AddOpenIddict().AddCore().UseEntityFrameworkCore()'.</value>
</data>
<data name="ID0275" xml:space="preserve">
<value>No default scope entity type was configured in the OpenIddict core options, which generally indicates that no scope store was registered in the DI container.
To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' package and call 'services.AddOpenIddict().AddCore().UseEntityFrameworkCore()'.</value>
</data>
<data name="ID0276" xml:space="preserve">
<value>No default token entity type was configured in the OpenIddict core options, which generally indicates that no token store was registered in the DI container.
To register the Entity Framework Core stores, reference the 'OpenIddict.EntityFrameworkCore' package and call 'services.AddOpenIddict().AddCore().UseEntityFrameworkCore()'.</value>
</data>
<data name="ID0277" xml:space="preserve">
<value>The Entity Framework 6.x stores cannot be used with generic types.
@ -1728,6 +1644,20 @@ To extract pushed authorization requests, create a class implementing 'IOpenIddi
<data name="ID0469" xml:space="preserve">
<value>The pushed authorization response was not correctly applied.
To apply pushed authorization responses, create a class implementing 'IOpenIddictServerHandler&lt;ApplyPushedAuthorizationResponseContext&gt;' and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.</value>
</data>
<data name="ID0470" xml:space="preserve">
<value>The Entity Framework 6.x context configured to be used with OpenIddict cannot be resolved from the dependency injection container.
Ensure the context is correctly registered using 'services.AddScoped(() => new ApplicationDbContext(connectionString))'.</value>
</data>
<data name="ID0471" xml:space="preserve">
<value>The Entity Framework Core context configured to be used with OpenIddict cannot be resolved from the dependency injection container.
Ensure the context is correctly registered using 'services.AddDbContext&lt;ApplicationDbContext&gt;()'.</value>
</data>
<data name="ID0472" xml:space="preserve">
<value>No database integration was configured to be used with OpenIddict.
To use the Entity Framework 6.x integration, reference the 'OpenIddict.EntityFramework' package and call 'services.AddOpenIddict().AddCore().UseEntityFramework()'.
To use the Entity Framework Core integration, reference the 'OpenIddict.EntityFrameworkCore' package and call 'services.AddOpenIddict().AddCore().UseEntityFrameworkCore()'.
To use the MongoDB integration, reference the 'OpenIddict.MongoDb' package and call 'services.AddOpenIddict().AddCore().UseMongoDb()'.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>

14
src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs

@ -1593,13 +1593,14 @@ public static class OpenIddictExtensions
// values. When the individual values are not strings, their string representation is returned.
if (claim.ValueType is "JSON_ARRAY")
{
var element = JsonSerializer.Deserialize<JsonElement>(claim.Value);
if (element.ValueKind is not JsonValueKind.Array)
using var document = JsonDocument.Parse(claim.Value);
if (document.RootElement.ValueKind is not JsonValueKind.Array)
{
continue;
}
foreach (var item in element.EnumerateArray())
foreach (var item in document.RootElement.EnumerateArray())
{
var value = item.ToString();
if (!builder.Contains(value))
@ -1644,13 +1645,14 @@ public static class OpenIddictExtensions
// values. When the individual values are not strings, their string representation is returned.
if (claim.ValueType is "JSON_ARRAY")
{
var element = JsonSerializer.Deserialize<JsonElement>(claim.Value);
if (element.ValueKind is not JsonValueKind.Array)
using var document = JsonDocument.Parse(claim.Value);
if (document.RootElement.ValueKind is not JsonValueKind.Array)
{
continue;
}
foreach (var item in element.EnumerateArray())
foreach (var item in document.RootElement.EnumerateArray())
{
var value = item.ToString();
if (!builder.Contains(value))

55
src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs

@ -143,7 +143,7 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// If the parameter is any other JsonNode (e.g a JsonValue), serialize it
// to a JsonElement first to determine its actual JSON representation
// and extract the number of items if the element is a JSON array or object.
JsonNode value when JsonSerializer.SerializeToElement(value)
JsonNode value when JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.JsonNode)
is JsonElement { ValueKind: JsonValueKind.Array or JsonValueKind.Object } element
=> Count(element),
@ -286,8 +286,8 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// Otherwise, serialize both values to JsonElement and compare them.
var (left, right) => DeepEquals(
JsonSerializer.SerializeToElement(left, left.GetType()),
JsonSerializer.SerializeToElement(right, right.GetType()))
JsonSerializer.SerializeToElement(left, left.GetType(), OpenIddictSerializer.Default),
JsonSerializer.SerializeToElement(right, right.GetType(), OpenIddictSerializer.Default))
};
static bool DeepEquals(JsonElement left, JsonElement right)
@ -408,7 +408,8 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
JsonNode value when JsonSerializer.SerializeToElement(value,
OpenIddictSerializer.Default.JsonNode) is JsonElement element
=> GetHashCodeFromJsonElement(element),
// Otherwise, use the default hash code method.
@ -516,7 +517,7 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value)
JsonNode value when JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.JsonNode)
is JsonElement { ValueKind: JsonValueKind.Object } element
=> GetParametersFromJsonElement(element),
@ -569,7 +570,7 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value)
JsonNode value when JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.JsonNode)
is JsonElement { ValueKind: JsonValueKind.Array } element
=> GetParametersFromJsonElement(element),
@ -653,7 +654,7 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
JsonValue value when value.TryGetValue(out string? result) => result,
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
JsonNode value when JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.JsonNode) is JsonElement element
=> element.ValueKind switch
{
JsonValueKind.True => "true",
@ -690,7 +691,7 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode node when JsonSerializer.SerializeToElement(node)
JsonNode node when JsonSerializer.SerializeToElement(node, OpenIddictSerializer.Default.JsonNode)
is JsonElement { ValueKind: JsonValueKind.Object } element
=> element.TryGetProperty(name, out JsonElement property) ? new(property) : null,
@ -729,7 +730,7 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode node when JsonSerializer.SerializeToElement(node)
JsonNode node when JsonSerializer.SerializeToElement(node, OpenIddictSerializer.Default.JsonNode)
is JsonElement { ValueKind: JsonValueKind.Array } element
=> index < element.GetArrayLength() ? new(element) : null,
@ -851,7 +852,7 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
JsonNode value when JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.JsonNode) is JsonElement element
=> ConvertFromJsonElement(element),
// If the parameter is of a different type, return null to indicate the conversion failed.
@ -887,16 +888,21 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
JsonElement value => value,
// When the parameter is JsonNode, serialize it as a JsonElement.
JsonNode value => JsonSerializer.SerializeToElement(value),
JsonNode value => JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.JsonNode),
// When the parameter is a string starting with '{' or '[' (which would correspond
// to a JSON object or array), try to deserialize it to get a JsonElement instance.
string { Length: > 0 } value when value[0] is '{' or '[' =>
DeserializeElement(value) ??
DeserializeElement(JsonSerializer.Serialize(value)) ?? default,
DeserializeElement(JsonSerializer.Serialize(value, OpenIddictSerializer.Default.String)) ?? default,
// Otherwise, serialize it to get a JsonElement instance.
object value => JsonSerializer.SerializeToElement(value, value.GetType())
bool value => JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.Boolean),
long value => JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.Int64),
string value => JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.String),
string[] value => JsonSerializer.SerializeToElement(value, OpenIddictSerializer.Default.StringArray),
_ => default
};
static JsonElement? DeserializeElement(string value)
@ -947,7 +953,7 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
string?[] value => CreateArray(value),
// When the parameter is JsonElement, deserialize it as a JsonNode.
JsonElement value => JsonSerializer.Deserialize<JsonNode>(value),
JsonElement value => value.Deserialize(OpenIddictSerializer.Default.JsonNode),
// If the parameter is of a different type, return null to indicate the conversion failed.
_ => null
@ -968,14 +974,14 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
static JsonArray? CreateArray(string?[] values)
{
var array = new JsonArray();
var nodes = new JsonNode?[values.Length];
for (var index = 0; index < values.Length; index++)
{
array.Add(values[index]);
nodes[index] = values[index];
}
return array;
return [.. nodes];
}
}
@ -1049,7 +1055,8 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
JsonNode value when JsonSerializer.SerializeToElement(value,
OpenIddictSerializer.Default.JsonNode) is JsonElement element
=> ConvertFromJsonElement(element),
// If the parameter is of a different type, return null to indicate the conversion failed.
@ -1114,7 +1121,8 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
JsonNode value when JsonSerializer.SerializeToElement(value,
OpenIddictSerializer.Default.JsonNode) is JsonElement element
=> ConvertFromJsonElement(element),
// If the parameter is of a different type, return null to indicate the conversion failed.
@ -1196,7 +1204,8 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
JsonNode value when JsonSerializer.SerializeToElement(value,
OpenIddictSerializer.Default.JsonNode) is JsonElement element
=> ConvertFromJsonElement(element),
// If the parameter is of a different type, return null to indicate the conversion failed.
@ -1314,7 +1323,8 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
// When the parameter is a JsonNode (e.g a JsonValue wrapping a non-primitive type),
// serialize it to a JsonElement first to determine its actual JSON representation
// and apply the same logic as with non-wrapped JsonElement instances.
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
JsonNode value when JsonSerializer.SerializeToElement(value,
OpenIddictSerializer.Default.JsonNode) is JsonElement element
=> ConvertFromJsonElement(element),
// If the parameter is of a different type, return null to indicate the conversion failed.
@ -1484,7 +1494,8 @@ public readonly struct OpenIddictParameter : IEquatable<OpenIddictParameter>
JsonValue value when value.TryGetValue(out string? result)
=> string.IsNullOrEmpty(result),
JsonNode value when JsonSerializer.SerializeToElement(value) is JsonElement element
JsonNode value when JsonSerializer.SerializeToElement(value,
OpenIddictSerializer.Default.JsonNode) is JsonElement element
=> IsEmptyJsonElement(element),
_ => false

32
src/OpenIddict.Abstractions/Primitives/OpenIddictSerializer.cs

@ -0,0 +1,32 @@
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Microsoft.IdentityModel.Tokens;
namespace OpenIddict.Abstractions;
/// <summary>
/// Exposes <see cref="JsonTypeInfo{T}"/> properties for all
/// the OpenIddict types suitable for JSON serialization.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(JsonArray))]
[JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(JsonNode))]
[JsonSerializable(typeof(JsonObject))]
[JsonSerializable(typeof(JsonValue))]
[JsonSerializable(typeof(JsonWebKey))]
[JsonSerializable(typeof(JsonWebKeySet))]
[JsonSerializable(typeof(long))]
[JsonSerializable(typeof(OpenIddictMessage), TypeInfoPropertyName = "Message")]
[JsonSerializable(typeof(OpenIddictParameter), TypeInfoPropertyName = "Parameter")]
[JsonSerializable(typeof(OpenIddictRequest), TypeInfoPropertyName = "Request")]
[JsonSerializable(typeof(OpenIddictResponse), TypeInfoPropertyName = "Response")]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(string[]))]
public partial class OpenIddictSerializer : JsonSerializerContext
{
}

15
src/OpenIddict.Abstractions/Resolvers/IOpenIddictApplicationStoreResolver.cs

@ -1,15 +0,0 @@
namespace OpenIddict.Abstractions;
/// <summary>
/// Exposes a method allowing to resolve an application store.
/// </summary>
public interface IOpenIddictApplicationStoreResolver
{
/// <summary>
/// Returns an application store compatible with the specified application type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <returns>An <see cref="IOpenIddictApplicationStore{TApplication}"/>.</returns>
IOpenIddictApplicationStore<TApplication> Get<TApplication>() where TApplication : class;
}

15
src/OpenIddict.Abstractions/Resolvers/IOpenIddictAuthorizationStoreResolver.cs

@ -1,15 +0,0 @@
namespace OpenIddict.Abstractions;
/// <summary>
/// Exposes a method allowing to resolve an authorization store.
/// </summary>
public interface IOpenIddictAuthorizationStoreResolver
{
/// <summary>
/// Returns an authorization store compatible with the specified authorization type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <returns>An <see cref="IOpenIddictAuthorizationStore{TAuthorization}"/>.</returns>
IOpenIddictAuthorizationStore<TAuthorization> Get<TAuthorization>() where TAuthorization : class;
}

15
src/OpenIddict.Abstractions/Resolvers/IOpenIddictScopeStoreResolver.cs

@ -1,15 +0,0 @@
namespace OpenIddict.Abstractions;
/// <summary>
/// Exposes a method allowing to resolve a scope store.
/// </summary>
public interface IOpenIddictScopeStoreResolver
{
/// <summary>
/// Returns a scope store compatible with the specified scope type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <returns>An <see cref="IOpenIddictScopeStore{TScope}"/>.</returns>
IOpenIddictScopeStore<TScope> Get<TScope>() where TScope : class;
}

15
src/OpenIddict.Abstractions/Resolvers/IOpenIddictTokenStoreResolver.cs

@ -1,15 +0,0 @@
namespace OpenIddict.Abstractions;
/// <summary>
/// Exposes a method allowing to resolve a token store.
/// </summary>
public interface IOpenIddictTokenStoreResolver
{
/// <summary>
/// Returns a token store compatible with the specified token type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <returns>An <see cref="IOpenIddictTokenStore{TToken}"/>.</returns>
IOpenIddictTokenStore<TToken> Get<TToken>() where TToken : class;
}

4
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs

@ -1110,7 +1110,7 @@ public static partial class OpenIddictClientAspNetCoreHandlers
/// </summary>
public sealed class EnablePassthroughMode<TContext, TFilter> : IOpenIddictClientHandler<TContext>
where TContext : BaseRequestContext
where TFilter : IOpenIddictClientHandlerFilter<TContext>
where TFilter : class, IOpenIddictClientHandlerFilter<TContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
@ -1227,7 +1227,7 @@ public static partial class OpenIddictClientAspNetCoreHandlers
/// </summary>
public sealed class ProcessPassthroughErrorResponse<TContext, TFilter> : IOpenIddictClientHandler<TContext>
where TContext : BaseRequestContext
where TFilter : IOpenIddictClientHandlerFilter<TContext>
where TFilter : class, IOpenIddictClientHandlerFilter<TContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.

4
src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs

@ -1170,7 +1170,7 @@ public static partial class OpenIddictClientOwinHandlers
/// </summary>
public sealed class EnablePassthroughMode<TContext, TFilter> : IOpenIddictClientHandler<TContext>
where TContext : BaseRequestContext
where TFilter : IOpenIddictClientHandlerFilter<TContext>
where TFilter : class, IOpenIddictClientHandlerFilter<TContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
@ -1407,7 +1407,7 @@ public static partial class OpenIddictClientOwinHandlers
/// </summary>
public sealed class ProcessPassthroughErrorResponse<TContext, TFilter> : IOpenIddictClientHandler<TContext>
where TContext : BaseRequestContext
where TFilter : IOpenIddictClientHandlerFilter<TContext>
where TFilter : class, IOpenIddictClientHandlerFilter<TContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.

4
src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs

@ -1359,8 +1359,8 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
{
// Note: ReadFromJsonAsync() automatically validates the content encoding and transparently
// transcodes the response stream if a non-UTF-8 response is returned by the remote server.
context.Transaction.Response = await response.Content.ReadFromJsonAsync<OpenIddictResponse>(
cancellationToken: context.CancellationToken);
context.Transaction.Response = await response.Content.ReadFromJsonAsync(
OpenIddictSerializer.Default.Response, context.CancellationToken);
}
// If an exception is thrown at this stage, this likely means the returned response was not a valid

4
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs

@ -306,7 +306,9 @@ public static partial class OpenIddictClientWebIntegrationHandlers
{
// Trovo returns a 500 internal server error when using the standard
// "application/x-www-form-urlencoded" format and requires using JSON.
ProviderTypes.Trovo => JsonContent.Create(context.Transaction.Request,
ProviderTypes.Trovo => JsonContent.Create(
context.Transaction.Request,
OpenIddictSerializer.Default.Request,
new MediaTypeHeaderValue(MediaTypes.Json)
{
CharSet = Charsets.Utf8

6
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs

@ -282,8 +282,10 @@ public static partial class OpenIddictClientWebIntegrationHandlers
{
// The userinfo endpoints exposed by these providers are based on GraphQL,
// which requires sending the request parameters as a JSON payload:
ProviderTypes.Meetup or ProviderTypes.SubscribeStar
=> JsonContent.Create(context.Transaction.Request, new MediaTypeHeaderValue(MediaTypes.Json)
ProviderTypes.Meetup or ProviderTypes.SubscribeStar => JsonContent.Create(
context.Transaction.Request,
OpenIddictSerializer.Default.Request,
new MediaTypeHeaderValue(MediaTypes.Json)
{
CharSet = Charsets.Utf8
}),

53
src/OpenIddict.Client/OpenIddictClientConfiguration.cs

@ -76,23 +76,7 @@ public sealed class OpenIddictClientConfiguration : IPostConfigureOptions<OpenId
// hash based on the issuer URI and the provider name, if available.
if (string.IsNullOrEmpty(registration.RegistrationId))
{
using var algorithm = CryptoConfig.CreateFromName("OpenIddict SHA-256 Cryptographic Provider") switch
{
SHA256 result => result,
null => SHA256.Create(),
var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName))
};
TransformBlock(algorithm, registration.Issuer.AbsoluteUri);
if (!string.IsNullOrEmpty(registration.ProviderName))
{
TransformBlock(algorithm, registration.ProviderName);
}
algorithm.TransformFinalBlock([], 0, 0);
registration.RegistrationId = Base64UrlEncoder.Encode(algorithm.Hash);
registration.RegistrationId = ComputeDefaultRegistrationId(registration);
}
// Ensure the registration identifier doesn't contain U+001E or U+001F separators as they are
@ -322,11 +306,38 @@ public sealed class OpenIddictClientConfiguration : IPostConfigureOptions<OpenId
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void TransformBlock(HashAlgorithm algorithm, string input)
static string ComputeDefaultRegistrationId(OpenIddictClientRegistration registration)
{
var buffer = Encoding.UTF8.GetBytes(input);
algorithm.TransformBlock(buffer, 0, buffer.Length, outputBuffer: null, outputOffset: 0);
Debug.Assert(registration.Issuer is { IsAbsoluteUri: true }, SR.GetResourceString(SR.ID4013));
using var algorithm = CreateAlgorithm();
TransformBlock(algorithm, registration.Issuer.AbsoluteUri);
if (!string.IsNullOrEmpty(registration.ProviderName))
{
TransformBlock(algorithm, registration.ProviderName);
}
algorithm.TransformFinalBlock([], 0, 0);
return Base64UrlEncoder.Encode(algorithm.Hash);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The default implementation is always used when no custom algorithm was registered.")]
static SHA256 CreateAlgorithm() => CryptoConfig.CreateFromName("OpenIddict SHA-256 Cryptographic Provider") switch
{
SHA256 result => result,
null => SHA256.Create(),
var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName))
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void TransformBlock(HashAlgorithm algorithm, string input)
{
var buffer = Encoding.UTF8.GetBytes(input);
algorithm.TransformBlock(buffer, 0, buffer.Length, outputBuffer: null, outputOffset: 0);
}
}
}
}

98
src/OpenIddict.Client/OpenIddictClientHandlerDescriptor.cs

@ -7,6 +7,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict.Client;
@ -69,34 +70,16 @@ public sealed class OpenIddictClientHandlerDescriptor
/// <summary>
/// Adds the type of a handler filter to the filters list.
/// </summary>
/// <param name="type">The event handler filter type.</param>
/// <typeparam name="TFilter">The event handler filter type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> AddFilter(Type type)
public Builder<TContext> AddFilter<TFilter>()
where TFilter : class, IOpenIddictClientHandlerFilter<TContext>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(IOpenIddictClientHandlerFilter<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0104));
}
_filters.Add(type);
_filters.Add(typeof(TFilter));
return this;
}
/// <summary>
/// Adds the type of a handler filter to the filters list.
/// </summary>
/// <typeparam name="TFilter">The event handler filter type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> AddFilter<TFilter>()
where TFilter : IOpenIddictClientHandlerFilter<TContext>
=> AddFilter(typeof(TFilter));
/// <summary>
/// Imports the properties set on the specified descriptor.
/// </summary>
@ -136,8 +119,7 @@ public sealed class OpenIddictClientHandlerDescriptor
throw new ArgumentNullException(nameof(descriptor));
}
var type = descriptor.ServiceType;
if (!typeof(IOpenIddictClientHandler<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type))
if (!typeof(IOpenIddictClientHandler<TContext>).IsAssignableFrom(descriptor.ServiceType))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0104));
}
@ -182,24 +164,18 @@ public sealed class OpenIddictClientHandlerDescriptor
/// <param name="handler">The handler instance.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseInlineHandler(Func<TContext, ValueTask> handler)
{
if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
}
return UseSingletonHandler(new OpenIddictClientHandler<TContext>(handler));
}
=> UseSingletonHandler(new OpenIddictClientHandler<TContext>(handler ??
throw new ArgumentNullException(nameof(handler))));
/// <summary>
/// Configures the descriptor to use the specified scoped handler.
/// </summary>
/// <typeparam name="THandler">The handler type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseScopedHandler<THandler>()
where THandler : IOpenIddictClientHandler<TContext>
=> SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), typeof(THandler), ServiceLifetime.Scoped));
public Builder<TContext> UseScopedHandler<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>()
where THandler : class, IOpenIddictClientHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Scoped<THandler, THandler>());
/// <summary>
/// Configures the descriptor to use the specified scoped handler.
@ -207,27 +183,20 @@ public sealed class OpenIddictClientHandlerDescriptor
/// <typeparam name="THandler">The handler type.</typeparam>
/// <param name="factory">The factory used to create the handler.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseScopedHandler<THandler>(Func<IServiceProvider, object> factory)
where THandler : IOpenIddictClientHandler<TContext>
{
if (factory is null)
{
throw new ArgumentNullException(nameof(factory));
}
return SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), factory, ServiceLifetime.Scoped));
}
public Builder<TContext> UseScopedHandler<THandler>(Func<IServiceProvider, THandler> factory)
where THandler : class, IOpenIddictClientHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Scoped(factory ??
throw new ArgumentNullException(nameof(factory))));
/// <summary>
/// Configures the descriptor to use the specified singleton handler.
/// </summary>
/// <typeparam name="THandler">The handler type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseSingletonHandler<THandler>()
where THandler : IOpenIddictClientHandler<TContext>
=> SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), typeof(THandler), ServiceLifetime.Singleton));
public Builder<TContext> UseSingletonHandler<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>()
where THandler : class, IOpenIddictClientHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Singleton<THandler, THandler>());
/// <summary>
/// Configures the descriptor to use the specified singleton handler.
@ -235,17 +204,10 @@ public sealed class OpenIddictClientHandlerDescriptor
/// <typeparam name="THandler">The handler type.</typeparam>
/// <param name="factory">The factory used to create the handler.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseSingletonHandler<THandler>(Func<IServiceProvider, object> factory)
where THandler : IOpenIddictClientHandler<TContext>
{
if (factory is null)
{
throw new ArgumentNullException(nameof(factory));
}
return SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), factory, ServiceLifetime.Singleton));
}
public Builder<TContext> UseSingletonHandler<THandler>(Func<IServiceProvider, THandler> factory)
where THandler : class, IOpenIddictClientHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Singleton(factory ??
throw new ArgumentNullException(nameof(factory))));
/// <summary>
/// Configures the descriptor to use the specified singleton handler.
@ -254,15 +216,9 @@ public sealed class OpenIddictClientHandlerDescriptor
/// <param name="handler">The handler instance.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseSingletonHandler<THandler>(THandler handler)
where THandler : IOpenIddictClientHandler<TContext>
{
if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
}
return SetServiceDescriptor(new ServiceDescriptor(typeof(THandler), handler));
}
where THandler : class, IOpenIddictClientHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Singleton(handler ??
throw new ArgumentNullException(nameof(handler))));
/// <summary>
/// Build a new descriptor instance, based on the parameters that were previously set.

12
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -1774,7 +1774,8 @@ public static partial class OpenIddictClientHandlers
// Note: a unique claim using the special JSON_ARRAY claim value type is allowed
// if the individual elements of the parsed JSON array are all string values.
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize<JsonElement>(value) is { ValueKind: JsonValueKind.Array } element &&
JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.JsonElement)
is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),
// The following claims MUST be represented as unique numeric dates.
@ -3209,7 +3210,8 @@ public static partial class OpenIddictClientHandlers
// Note: a unique claim using the special JSON_ARRAY claim value type is allowed
// if the individual elements of the parsed JSON array are all string values.
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize<JsonElement>(value) is { ValueKind: JsonValueKind.Array } element &&
JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.JsonElement)
is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),
// The following claims MUST be represented as unique numeric dates.
@ -4477,7 +4479,8 @@ public static partial class OpenIddictClientHandlers
// Note: a unique claim using the special JSON_ARRAY claim value type is allowed
// if the individual elements of the parsed JSON array are all string values.
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize<JsonElement>(value) is { ValueKind: JsonValueKind.Array } element &&
JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.JsonElement)
is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),
// The following claims MUST be represented as unique integers.
@ -8035,7 +8038,8 @@ public static partial class OpenIddictClientHandlers
// Note: a unique claim using the special JSON_ARRAY claim value type is allowed
// if the individual elements of the parsed JSON array are all string values.
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize<JsonElement>(value) is { ValueKind: JsonValueKind.Array } element &&
JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.JsonElement)
is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),
// The following claims MUST be represented as unique integers.

9
src/OpenIddict.Core/Caches/OpenIddictApplicationCache.cs

@ -24,9 +24,14 @@ public sealed class OpenIddictApplicationCache<TApplication> : IOpenIddictApplic
private readonly ConcurrentDictionary<string, CancellationTokenSource> _signals;
private readonly IOpenIddictApplicationStore<TApplication> _store;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictApplicationCache{TApplication}"/> class.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictApplicationCache(
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictApplicationStoreResolver resolver)
IOpenIddictApplicationStore<TApplication> store)
{
_cache = new MemoryCache(new MemoryCacheOptions
{
@ -34,7 +39,7 @@ public sealed class OpenIddictApplicationCache<TApplication> : IOpenIddictApplic
});
_signals = new ConcurrentDictionary<string, CancellationTokenSource>(StringComparer.Ordinal);
_store = (resolver ?? throw new ArgumentNullException(nameof(resolver))).Get<TApplication>();
_store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <inheritdoc/>

9
src/OpenIddict.Core/Caches/OpenIddictAuthorizationCache.cs

@ -23,9 +23,14 @@ public sealed class OpenIddictAuthorizationCache<TAuthorization> : IOpenIddictAu
private readonly ConcurrentDictionary<string, CancellationTokenSource> _signals;
private readonly IOpenIddictAuthorizationStore<TAuthorization> _store;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictAuthorizationCache{TAuthorization}"/> class.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictAuthorizationCache(
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictAuthorizationStoreResolver resolver)
IOpenIddictAuthorizationStore<TAuthorization> store)
{
_cache = new MemoryCache(new MemoryCacheOptions
{
@ -33,7 +38,7 @@ public sealed class OpenIddictAuthorizationCache<TAuthorization> : IOpenIddictAu
});
_signals = new ConcurrentDictionary<string, CancellationTokenSource>(StringComparer.Ordinal);
_store = (resolver ?? throw new ArgumentNullException(nameof(resolver))).Get<TAuthorization>();
_store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <inheritdoc/>

9
src/OpenIddict.Core/Caches/OpenIddictScopeCache.cs

@ -23,9 +23,14 @@ public sealed class OpenIddictScopeCache<TScope> : IOpenIddictScopeCache<TScope>
private readonly ConcurrentDictionary<string, CancellationTokenSource> _signals;
private readonly IOpenIddictScopeStore<TScope> _store;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictScopeCache{TScope}"/> class.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictScopeCache(
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictScopeStoreResolver resolver)
IOpenIddictScopeStore<TScope> store)
{
_cache = new MemoryCache(new MemoryCacheOptions
{
@ -33,7 +38,7 @@ public sealed class OpenIddictScopeCache<TScope> : IOpenIddictScopeCache<TScope>
});
_signals = new ConcurrentDictionary<string, CancellationTokenSource>(StringComparer.Ordinal);
_store = (resolver ?? throw new ArgumentNullException(nameof(resolver))).Get<TScope>();
_store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <inheritdoc/>

9
src/OpenIddict.Core/Caches/OpenIddictTokenCache.cs

@ -23,9 +23,14 @@ public sealed class OpenIddictTokenCache<TToken> : IOpenIddictTokenCache<TToken>
private readonly ConcurrentDictionary<string, CancellationTokenSource> _signals;
private readonly IOpenIddictTokenStore<TToken> _store;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictTokenCache{TToken}"/> class.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictTokenCache(
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictTokenStoreResolver resolver)
IOpenIddictTokenStore<TToken> store)
{
_cache = new MemoryCache(new MemoryCacheOptions
{
@ -33,7 +38,7 @@ public sealed class OpenIddictTokenCache<TToken> : IOpenIddictTokenCache<TToken>
});
_signals = new ConcurrentDictionary<string, CancellationTokenSource>(StringComparer.Ordinal);
_store = (resolver ?? throw new ArgumentNullException(nameof(resolver))).Get<TToken>();
_store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <inheritdoc/>

14
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -33,22 +33,28 @@ namespace OpenIddict.Core;
/// </summary>
/// <remarks>
/// Applications that do not want to depend on a specific entity type can use the non-generic
/// <see cref="IOpenIddictApplicationManager"/> instead, for which the actual entity type
/// is resolved at runtime based on the default entity type registered in the core options.
/// <see cref="IOpenIddictApplicationManager"/> instead, for which the actual entity type is resolved at runtime.
/// </remarks>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
public class OpenIddictApplicationManager<TApplication> : IOpenIddictApplicationManager where TApplication : class
{
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictApplicationManager{TApplication}"/> class.
/// </summary>
/// <param name="cache">The cache.</param>
/// <param name="logger">The logger.</param>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictApplicationManager(
IOpenIddictApplicationCache<TApplication> cache,
ILogger<OpenIddictApplicationManager<TApplication>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictApplicationStoreResolver resolver)
IOpenIddictApplicationStore<TApplication> store)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Options = options ?? throw new ArgumentNullException(nameof(options));
Store = (resolver ?? throw new ArgumentNullException(nameof(resolver))).Get<TApplication>();
Store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <summary>

14
src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs

@ -23,22 +23,28 @@ namespace OpenIddict.Core;
/// </summary>
/// <remarks>
/// Applications that do not want to depend on a specific entity type can use the non-generic
/// <see cref="IOpenIddictAuthorizationManager"/> instead, for which the actual entity type
/// is resolved at runtime based on the default entity type registered in the core options.
/// <see cref="IOpenIddictAuthorizationManager"/> instead, for which the actual entity type is resolved at runtime.
/// </remarks>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
public class OpenIddictAuthorizationManager<TAuthorization> : IOpenIddictAuthorizationManager where TAuthorization : class
{
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictAuthorizationManager{TAuthorization}"/> class.
/// </summary>
/// <param name="cache">The cache.</param>
/// <param name="logger">The logger.</param>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictAuthorizationManager(
IOpenIddictAuthorizationCache<TAuthorization> cache,
ILogger<OpenIddictAuthorizationManager<TAuthorization>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictAuthorizationStoreResolver resolver)
IOpenIddictAuthorizationStore<TAuthorization> store)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Options = options ?? throw new ArgumentNullException(nameof(options));
Store = (resolver ?? throw new ArgumentNullException(nameof(resolver))).Get<TAuthorization>();
Store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <summary>

14
src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs

@ -21,22 +21,28 @@ namespace OpenIddict.Core;
/// </summary>
/// <remarks>
/// Applications that do not want to depend on a specific entity type can use the non-generic
/// <see cref="IOpenIddictScopeManager"/> instead, for which the actual entity type
/// is resolved at runtime based on the default entity type registered in the core options.
/// <see cref="IOpenIddictScopeManager"/> instead, for which the actual entity type is resolved at runtime.
/// </remarks>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
public class OpenIddictScopeManager<TScope> : IOpenIddictScopeManager where TScope : class
{
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictScopeManager{TScope}"/> class.
/// </summary>
/// <param name="cache">The cache.</param>
/// <param name="logger">The logger.</param>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictScopeManager(
IOpenIddictScopeCache<TScope> cache,
ILogger<OpenIddictScopeManager<TScope>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictScopeStoreResolver resolver)
IOpenIddictScopeStore<TScope> store)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Options = options ?? throw new ArgumentNullException(nameof(options));
Store = (resolver ?? throw new ArgumentNullException(nameof(resolver))).Get<TScope>();
Store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <summary>

14
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -22,22 +22,28 @@ namespace OpenIddict.Core;
/// </summary>
/// <remarks>
/// Applications that do not want to depend on a specific entity type can use the non-generic
/// <see cref="IOpenIddictTokenManager"/> instead, for which the actual entity type
/// is resolved at runtime based on the default entity type registered in the core options.
/// <see cref="IOpenIddictTokenManager"/> instead, for which the actual entity type is resolved at runtime.
/// </remarks>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
public class OpenIddictTokenManager<TToken> : IOpenIddictTokenManager where TToken : class
{
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictTokenManager{TToken}"/> class.
/// </summary>
/// <param name="cache">The cache.</param>
/// <param name="logger">The logger.</param>
/// <param name="options">The options.</param>
/// <param name="store">The store.</param>
public OpenIddictTokenManager(
IOpenIddictTokenCache<TToken> cache,
ILogger<OpenIddictTokenManager<TToken>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictTokenStoreResolver resolver)
IOpenIddictTokenStore<TToken> store)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Options = options ?? throw new ArgumentNullException(nameof(options));
Store = (resolver ?? throw new ArgumentNullException(nameof(resolver))).Get<TToken>();
Store = store ?? throw new ArgumentNullException(nameof(store));
}
/// <summary>

678
src/OpenIddict.Core/OpenIddictCoreBuilder.cs

@ -5,9 +5,9 @@
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenIddict.Core;
using OpenIddict.Extensions;
namespace Microsoft.Extensions.DependencyInjection;
@ -48,577 +48,229 @@ public sealed class OpenIddictCoreBuilder
}
/// <summary>
/// Adds a custom application store by a custom implementation derived
/// from <see cref="IOpenIddictApplicationStore{TApplication}"/>.
/// Note: when using this overload, the application store
/// must be either a non-generic or closed generic service.
/// Replaces the application manager by the specified type.
/// </summary>
/// <typeparam name="TStore">The type of the custom store.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <typeparam name="TApplication">The type of the entity.</typeparam>
/// <typeparam name="TManager">The type of the manager.</typeparam>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder AddApplicationStore<TStore>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TStore : class
=> AddApplicationStore(typeof(TStore), lifetime);
/// <summary>
/// Adds a custom application store by a custom implementation derived
/// from <see cref="IOpenIddictApplicationStore{TApplication}"/>.
/// Note: when using this overload, the application store can be
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom store.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder AddApplicationStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
public OpenIddictCoreBuilder ReplaceApplicationManager<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TManager>()
where TApplication : class
where TManager : OpenIddictApplicationManager<TApplication>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictApplicationStore<>)) ??
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
// Note: managers can be either open generics (e.g OpenIddictApplicationStore<>)
// or closed generics (e.g OpenIddictApplicationStore<OpenIddictApplication>).
if (type.IsGenericTypeDefinition)
{
if (type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStore<>), type, lifetime));
}
else
{
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
Services.Replace(ServiceDescriptor.Scoped<OpenIddictApplicationManager<TApplication>, TManager>());
return this;
}
/// <summary>
/// Adds a custom authorization store by a custom implementation derived
/// from <see cref="IOpenIddictAuthorizationStore{TAuthorization}"/>.
/// Note: when using this overload, the authorization store
/// must be either a non-generic or closed generic service.
/// </summary>
/// <typeparam name="TStore">The type of the custom store.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder AddAuthorizationStore<TStore>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TStore : class
=> AddAuthorizationStore(typeof(TStore), lifetime);
/// <summary>
/// Adds a custom authorization store by a custom implementation derived
/// from <see cref="IOpenIddictAuthorizationStore{TAuthorization}"/>.
/// Note: when using this overload, the authorization store can be
/// either a non-generic, a closed or an open generic service.
/// Replaces the application manager by the specified type.
/// </summary>
/// <param name="type">The type of the custom store.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <remarks>
/// Note: the specified type MUST be an open generic type definition containing exactly one generic argument.
/// </remarks>
/// <param name="type">The type of the manager.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder AddAuthorizationStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
public OpenIddictCoreBuilder ReplaceApplicationManager(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
{
if (type is null)
if (!type.IsGenericTypeDefinition || type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentNullException(nameof(type));
}
var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictAuthorizationStore<>)) ??
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
// Note: managers can be either open generics (e.g OpenIddictAuthorizationStore<>)
// or closed generics (e.g OpenIddictAuthorizationStore<OpenIddictAuthorization>).
if (type.IsGenericTypeDefinition)
{
if (type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStore<>), type, lifetime));
}
else
{
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>), type));
return this;
}
/// <summary>
/// Adds a custom scope store by a custom implementation derived
/// from <see cref="IOpenIddictScopeStore{TScope}"/>.
/// Note: when using this overload, the scope store
/// must be either a non-generic or closed generic service.
/// Replaces the application store by the specified type.
/// </summary>
/// <typeparam name="TStore">The type of the custom store.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <typeparam name="TApplication">The type of the entity.</typeparam>
/// <typeparam name="TStore">The type of the store.</typeparam>
/// <param name="lifetime">The lifetime of the store.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder AddScopeStore<TStore>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TStore : class
=> AddScopeStore(typeof(TStore), lifetime);
/// <summary>
/// Adds a custom scope store by a custom implementation derived
/// from <see cref="IOpenIddictScopeStore{TScope}"/>.
/// Note: when using this overload, the scope store can be
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom store.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder AddScopeStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
public OpenIddictCoreBuilder ReplaceApplicationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TStore>(
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TApplication : class
where TStore : IOpenIddictApplicationStore<TApplication>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictScopeStore<>)) ??
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
// Note: managers can be either open generics (e.g OpenIddictScopeStore<>)
// or closed generics (e.g OpenIddictScopeStore<OpenIddictScope>).
if (type.IsGenericTypeDefinition)
{
if (type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStore<>), type, lifetime));
}
else
{
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
Services.Replace(ServiceDescriptor.Describe(typeof(IOpenIddictApplicationStore<TApplication>), typeof(TStore), lifetime));
return this;
}
/// <summary>
/// Adds a custom token store by a custom implementation derived
/// from <see cref="IOpenIddictTokenStore{TToken}"/>.
/// Note: when using this overload, the token store
/// must be either a non-generic or closed generic service.
/// </summary>
/// <typeparam name="TStore">The type of the custom store.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder AddTokenStore<TStore>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TStore : class
=> AddTokenStore(typeof(TStore), lifetime);
/// <summary>
/// Adds a custom token store by a custom implementation derived
/// from <see cref="IOpenIddictTokenStore{TToken}"/>.
/// Note: when using this overload, the token store can be
/// either a non-generic, a closed or an open generic service.
/// Replaces the authorization manager by the specified type.
/// </summary>
/// <param name="type">The type of the custom store.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <typeparam name="TAuthorization">The type of the entity.</typeparam>
/// <typeparam name="TManager">The type of the manager.</typeparam>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder AddTokenStore(Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
public OpenIddictCoreBuilder ReplaceAuthorizationManager<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TManager>()
where TAuthorization : class
where TManager : OpenIddictAuthorizationManager<TAuthorization>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(IOpenIddictTokenStore<>)) ??
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
// Note: managers can be either open generics (e.g OpenIddictTokenStore<>)
// or closed generics (e.g OpenIddictTokenStore<OpenIddictToken>).
if (type.IsGenericTypeDefinition)
{
if (type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStore<>), type, lifetime));
}
else
{
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
Services.Replace(ServiceDescriptor.Scoped<OpenIddictAuthorizationManager<TAuthorization>, TManager>());
return this;
}
/// <summary>
/// Replace the default application manager by a custom manager derived
/// from <see cref="OpenIddictApplicationManager{TApplication}"/>.
/// Note: when using this overload, the application manager
/// must be either a non-generic or closed generic service.
/// Replaces the authorization manager by the specified type.
/// </summary>
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <remarks>
/// Note: the specified type MUST be an open generic type definition containing exactly one generic argument.
/// </remarks>
/// <param name="type">The type of the manager.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceApplicationManager<TManager>()
where TManager : class
=> ReplaceApplicationManager(typeof(TManager));
/// <summary>
/// Replace the default application manager by a custom manager derived
/// from <see cref="OpenIddictApplicationManager{TApplication}"/>.
/// Note: when using this overload, the application manager can be
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom manager.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceApplicationManager(Type type)
public OpenIddictCoreBuilder ReplaceAuthorizationManager(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
{
if (type is null)
if (!type.IsGenericTypeDefinition || type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentNullException(nameof(type));
}
var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictApplicationManager<>)) ??
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
// Note: managers can be either open generics (e.g OpenIddictApplicationManager<>)
// or closed generics (e.g OpenIddictApplicationManager<OpenIddictApplication>).
if (type.IsGenericTypeDefinition)
{
if (type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(type, type));
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>), type));
}
else
{
object ResolveManager(IServiceProvider provider)
=> provider.GetRequiredService(typeof(OpenIddictApplicationManager<>)
.MakeGenericType(root.GenericTypeArguments[0]));
Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager));
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>), type));
return this;
}
/// <summary>
/// Replaces the default application store resolver by a custom implementation.
/// </summary>
/// <typeparam name="TResolver">The type of the custom store.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceApplicationStoreResolver<TResolver>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TResolver : IOpenIddictApplicationStoreResolver
=> ReplaceApplicationStoreResolver(typeof(TResolver), lifetime);
/// <summary>
/// Replaces the default application store resolver by a custom implementation.
/// Replaces the authorization store by the specified type.
/// </summary>
/// <param name="type">The type of the custom store.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <typeparam name="TAuthorization">The type of the entity.</typeparam>
/// <typeparam name="TStore">The type of the store.</typeparam>
/// <param name="lifetime">The lifetime of the store.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceApplicationStoreResolver(
Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
public OpenIddictCoreBuilder ReplaceAuthorizationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TStore>(
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TAuthorization : class
where TStore : IOpenIddictAuthorizationStore<TAuthorization>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(IOpenIddictApplicationStoreResolver).IsAssignableFrom(type))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStoreResolver), type, lifetime));
Services.Replace(ServiceDescriptor.Describe(typeof(IOpenIddictAuthorizationStore<TAuthorization>), typeof(TStore), lifetime));
return this;
}
/// <summary>
/// Replace the default authorization manager by a custom manager derived
/// from <see cref="OpenIddictAuthorizationManager{TAuthorization}"/>.
/// Note: when using this overload, the authorization manager
/// must be either a non-generic or closed generic service.
/// Replaces the scope manager by the specified type.
/// </summary>
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <typeparam name="TScope">The type of the entity.</typeparam>
/// <typeparam name="TManager">The type of the manager.</typeparam>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceAuthorizationManager<TManager>()
where TManager : class
=> ReplaceAuthorizationManager(typeof(TManager));
/// <summary>
/// Replace the default authorization manager by a custom manager derived
/// from <see cref="OpenIddictAuthorizationManager{TAuthorization}"/>.
/// Note: when using this overload, the authorization manager can be
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom manager.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceAuthorizationManager(Type type)
public OpenIddictCoreBuilder ReplaceScopeManager<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TManager>()
where TScope : class
where TManager : OpenIddictScopeManager<TScope>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictAuthorizationManager<>)) ??
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
// Note: managers can be either open generics (e.g OpenIddictAuthorizationManager<>)
// or closed generics (e.g OpenIddictAuthorizationManager<OpenIddictAuthorization>).
if (type.IsGenericTypeDefinition)
{
if (type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(type, type));
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>), type));
}
else
{
object ResolveManager(IServiceProvider provider)
=> provider.GetRequiredService(typeof(OpenIddictAuthorizationManager<>)
.MakeGenericType(root.GenericTypeArguments[0]));
Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager));
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
}
Services.Replace(ServiceDescriptor.Scoped<OpenIddictScopeManager<TScope>, TManager>());
return this;
}
/// <summary>
/// Replaces the default authorization store resolver by a custom implementation.
/// </summary>
/// <typeparam name="TResolver">The type of the custom store.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver<TResolver>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TResolver : IOpenIddictAuthorizationStoreResolver
=> ReplaceAuthorizationStoreResolver(typeof(TResolver), lifetime);
/// <summary>
/// Replaces the default authorization store resolver by a custom implementation.
/// Replaces the scope manager by the specified type.
/// </summary>
/// <param name="type">The type of the custom store.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <remarks>
/// Note: the specified type MUST be an open generic type definition containing exactly one generic argument.
/// </remarks>
/// <param name="type">The type of the manager.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver(
Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
public OpenIddictCoreBuilder ReplaceScopeManager(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(IOpenIddictAuthorizationStoreResolver).IsAssignableFrom(type))
if (!type.IsGenericTypeDefinition || type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStoreResolver), type, lifetime));
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>), type));
return this;
}
/// <summary>
/// Replace the default scope manager by a custom manager
/// derived from <see cref="OpenIddictScopeManager{TScope}"/>.
/// Note: when using this overload, the scope manager
/// must be either a non-generic or closed generic service.
/// Replaces the scope store by the specified type.
/// </summary>
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <typeparam name="TScope">The type of the entity.</typeparam>
/// <typeparam name="TStore">The type of the store.</typeparam>
/// <param name="lifetime">The lifetime of the store.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceScopeManager<TManager>()
where TManager : class
=> ReplaceScopeManager(typeof(TManager));
/// <summary>
/// Replace the default scope manager by a custom manager
/// derived from <see cref="OpenIddictScopeManager{TScope}"/>.
/// Note: when using this overload, the scope manager can be
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom manager.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceScopeManager(Type type)
public OpenIddictCoreBuilder ReplaceScopeStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TStore>(
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TScope : class
where TStore : IOpenIddictScopeStore<TScope>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictScopeManager<>)) ??
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
// Note: managers can be either open generics (e.g OpenIddictScopeManager<>)
// or closed generics (e.g OpenIddictScopeManager<OpenIddictScope>).
if (type.IsGenericTypeDefinition)
{
if (type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(type, type));
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>), type));
}
else
{
object ResolveManager(IServiceProvider provider)
=> provider.GetRequiredService(typeof(OpenIddictScopeManager<>)
.MakeGenericType(root.GenericTypeArguments[0]));
Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager));
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
}
Services.Replace(ServiceDescriptor.Describe(typeof(IOpenIddictScopeStore<TScope>), typeof(TStore), lifetime));
return this;
}
/// <summary>
/// Replaces the default scope store resolver by a custom implementation.
/// </summary>
/// <typeparam name="TResolver">The type of the custom store.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceScopeStoreResolver<TResolver>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TResolver : IOpenIddictScopeStoreResolver
=> ReplaceScopeStoreResolver(typeof(TResolver), lifetime);
/// <summary>
/// Replaces the default scope store resolver by a custom implementation.
/// Replaces the token manager by the specified type.
/// </summary>
/// <param name="type">The type of the custom store.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <typeparam name="TToken">The type of the entity.</typeparam>
/// <typeparam name="TManager">The type of the manager.</typeparam>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceScopeStoreResolver(
Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
public OpenIddictCoreBuilder ReplaceTokenManager<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TManager>()
where TToken : class
where TManager : OpenIddictTokenManager<TToken>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(IOpenIddictScopeStoreResolver).IsAssignableFrom(type))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStoreResolver), type, lifetime));
Services.Replace(ServiceDescriptor.Scoped<OpenIddictTokenManager<TToken>, TManager>());
return this;
}
/// <summary>
/// Replace the default token manager by a custom manager
/// derived from <see cref="OpenIddictTokenManager{TToken}"/>.
/// Note: when using this overload, the token manager
/// must be either a non-generic or closed generic service.
/// Replaces the token manager by the specified type.
/// </summary>
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <remarks>
/// Note: the specified type MUST be an open generic type definition containing exactly one generic argument.
/// </remarks>
/// <param name="type">The type of the manager.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceTokenManager<TManager>()
where TManager : class
=> ReplaceTokenManager(typeof(TManager));
/// <summary>
/// Replace the default token manager by a custom manager
/// derived from <see cref="OpenIddictTokenManager{TToken}"/>.
/// Note: when using this overload, the token manager can be
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom manager.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceTokenManager(Type type)
public OpenIddictCoreBuilder ReplaceTokenManager(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
{
if (type is null)
if (!type.IsGenericTypeDefinition || type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentNullException(nameof(type));
}
var root = OpenIddictHelpers.FindGenericBaseType(type, typeof(OpenIddictTokenManager<>)) ??
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
// Note: managers can be either open generics (e.g OpenIddictTokenManager<>)
// or closed generics (e.g OpenIddictTokenManager<OpenIddictToken>).
if (type.IsGenericTypeDefinition)
{
if (type.GetGenericArguments() is not { Length: 1 })
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(type, type));
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>), type));
}
else
{
object ResolveManager(IServiceProvider provider)
=> provider.GetRequiredService(typeof(OpenIddictTokenManager<>)
.MakeGenericType(root.GenericTypeArguments[0]));
Services.Replace(ServiceDescriptor.Scoped(type, ResolveManager));
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>), type));
return this;
}
/// <summary>
/// Replaces the default token store resolver by a custom implementation.
/// </summary>
/// <typeparam name="TResolver">The type of the custom store.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceTokenStoreResolver<TResolver>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TResolver : IOpenIddictTokenStoreResolver
=> ReplaceTokenStoreResolver(typeof(TResolver), lifetime);
/// <summary>
/// Replaces the default token store resolver by a custom implementation.
/// Replaces the token store by the specified type.
/// </summary>
/// <param name="type">The type of the custom store.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <typeparam name="TToken">The type of the entity.</typeparam>
/// <typeparam name="TStore">The type of the store.</typeparam>
/// <param name="lifetime">The lifetime of the store.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder ReplaceTokenStoreResolver(
Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
public OpenIddictCoreBuilder ReplaceTokenStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TStore>(
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TToken : class
where TStore : IOpenIddictTokenStore<TToken>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(IOpenIddictTokenStoreResolver).IsAssignableFrom(type))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStoreResolver), type, lifetime));
Services.Replace(ServiceDescriptor.Describe(typeof(IOpenIddictTokenStore<TToken>), typeof(TStore), lifetime));
return this;
}
@ -647,108 +299,52 @@ public sealed class OpenIddictCoreBuilder
/// Configures OpenIddict to use the specified entity as the default application entity.
/// </summary>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder SetDefaultApplicationEntity<TApplication>() where TApplication : class
=> SetDefaultApplicationEntity(typeof(TApplication));
/// <summary>
/// Configures OpenIddict to use the specified entity as the default application entity.
/// </summary>
/// <param name="type">The application entity type.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder SetDefaultApplicationEntity(Type type)
public OpenIddictCoreBuilder SetDefaultApplicationEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication>() where TApplication : class
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (type.IsValueType)
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictApplicationManager>(static provider =>
provider.GetRequiredService<OpenIddictApplicationManager<TApplication>>()));
return Configure(options => options.DefaultApplicationType = type);
return this;
}
/// <summary>
/// Configures OpenIddict to use the specified entity as the default authorization entity.
/// </summary>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder SetDefaultAuthorizationEntity<TAuthorization>() where TAuthorization : class
=> SetDefaultAuthorizationEntity(typeof(TAuthorization));
/// <summary>
/// Configures OpenIddict to use the specified entity as the default authorization entity.
/// </summary>
/// <param name="type">The authorization entity type.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder SetDefaultAuthorizationEntity(Type type)
public OpenIddictCoreBuilder SetDefaultAuthorizationEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization>() where TAuthorization : class
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (type.IsValueType)
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictAuthorizationManager>(static provider =>
provider.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>()));
return Configure(options => options.DefaultAuthorizationType = type);
return this;
}
/// <summary>
/// Configures OpenIddict to use the specified entity as the default scope entity.
/// </summary>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder SetDefaultScopeEntity<TScope>() where TScope : class
=> SetDefaultScopeEntity(typeof(TScope));
/// <summary>
/// Configures OpenIddict to use the specified entity as the default scope entity.
/// </summary>
/// <param name="type">The scope entity type.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder SetDefaultScopeEntity(Type type)
public OpenIddictCoreBuilder SetDefaultScopeEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope>() where TScope : class
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (type.IsValueType)
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeManager>(static provider =>
provider.GetRequiredService<OpenIddictScopeManager<TScope>>()));
return Configure(options => options.DefaultScopeType = type);
return this;
}
/// <summary>
/// Configures OpenIddict to use the specified entity as the default token entity.
/// </summary>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder SetDefaultTokenEntity<TToken>() where TToken : class
=> SetDefaultTokenEntity(typeof(TToken));
/// <summary>
/// Configures OpenIddict to use the specified entity as the default token entity.
/// </summary>
/// <param name="type">The token entity type.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/> instance.</returns>
public OpenIddictCoreBuilder SetDefaultTokenEntity(Type type)
public OpenIddictCoreBuilder SetDefaultTokenEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken>() where TToken : class
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (type.IsValueType)
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenManager>(static provider =>
provider.GetRequiredService<OpenIddictTokenManager<TToken>>()));
return Configure(options => options.DefaultTokenType = type);
return this;
}
/// <summary>

66
src/OpenIddict.Core/OpenIddictCoreExtensions.cs

@ -5,12 +5,11 @@
*/
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
namespace Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
/// <summary>
/// Exposes extensions allowing to register the OpenIddict core services.
/// </summary>
@ -33,60 +32,27 @@ public static class OpenIddictCoreExtensions
builder.Services.AddMemoryCache();
builder.Services.AddOptions();
builder.Services.TryAddScoped(typeof(OpenIddictApplicationManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictScopeManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictTokenManager<>));
builder.Services.TryAddScoped(typeof(IOpenIddictApplicationCache<>), typeof(OpenIddictApplicationCache<>));
builder.Services.TryAddScoped(typeof(IOpenIddictAuthorizationCache<>), typeof(OpenIddictAuthorizationCache<>));
builder.Services.TryAddScoped(typeof(IOpenIddictScopeCache<>), typeof(OpenIddictScopeCache<>));
builder.Services.TryAddScoped(typeof(IOpenIddictTokenCache<>), typeof(OpenIddictTokenCache<>));
builder.Services.TryAddScoped<IOpenIddictApplicationStoreResolver, OpenIddictApplicationStoreResolver>();
builder.Services.TryAddScoped<IOpenIddictAuthorizationStoreResolver, OpenIddictAuthorizationStoreResolver>();
builder.Services.TryAddScoped<IOpenIddictScopeStoreResolver, OpenIddictScopeStoreResolver>();
builder.Services.TryAddScoped<IOpenIddictTokenStoreResolver, OpenIddictTokenStoreResolver>();
builder.Services.TryAddScoped(static provider =>
{
var type = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>()
.CurrentValue?.DefaultApplicationType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0273));
return (IOpenIddictApplicationManager) provider.GetRequiredService(
typeof(OpenIddictApplicationManager<>).MakeGenericType(type));
});
builder.Services.TryAddScoped(static provider =>
{
var type = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>()
.CurrentValue?.DefaultAuthorizationType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0274));
return (IOpenIddictAuthorizationManager) provider.GetRequiredService(
typeof(OpenIddictAuthorizationManager<>).MakeGenericType(type));
});
builder.Services.TryAddScoped(static provider =>
{
var type = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>()
.CurrentValue?.DefaultScopeType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0275));
return (IOpenIddictScopeManager) provider.GetRequiredService(
typeof(OpenIddictScopeManager<>).MakeGenericType(type));
});
builder.Services.TryAddScoped(static provider =>
{
var type = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>()
.CurrentValue?.DefaultTokenType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0276));
builder.Services.TryAddScoped(typeof(OpenIddictApplicationManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictScopeManager<>));
builder.Services.TryAddScoped(typeof(OpenIddictTokenManager<>));
return (IOpenIddictTokenManager) provider.GetRequiredService(
typeof(OpenIddictTokenManager<>).MakeGenericType(type));
});
// Note: default factories for the untyped managers are always registered to make debugging
// easier if no store was configured. It is expected that store implementations replace the
// registrations with working implementation factories that use the correct entity types.
builder.Services.TryAddScoped<IOpenIddictApplicationManager>(static provider =>
throw new InvalidOperationException(SR.GetResourceString(SR.ID0472)));
builder.Services.TryAddScoped<IOpenIddictAuthorizationManager>(static provider =>
throw new InvalidOperationException(SR.GetResourceString(SR.ID0472)));
builder.Services.TryAddScoped<IOpenIddictScopeManager>(static provider =>
throw new InvalidOperationException(SR.GetResourceString(SR.ID0472)));
builder.Services.TryAddScoped<IOpenIddictTokenManager>(static provider =>
throw new InvalidOperationException(SR.GetResourceString(SR.ID0472)));
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<

26
src/OpenIddict.Core/OpenIddictCoreOptions.cs

@ -4,6 +4,8 @@
* the license and the contributors participating to this project.
*/
using System.Diagnostics.CodeAnalysis;
namespace OpenIddict.Core;
/// <summary>
@ -11,30 +13,6 @@ namespace OpenIddict.Core;
/// </summary>
public sealed class OpenIddictCoreOptions
{
/// <summary>
/// Gets or sets the type corresponding to the default Application entity,
/// used by the non-generic application manager and the server/validation services.
/// </summary>
public Type? DefaultApplicationType { get; set; }
/// <summary>
/// Gets or sets the type corresponding to the default Authorization entity,
/// used by the non-generic authorization manager and the server/validation services.
/// </summary>
public Type? DefaultAuthorizationType { get; set; }
/// <summary>
/// Gets or sets the type corresponding to the default Scope entity,
/// used by the non-generic scope manager and the server/validation services.
/// </summary>
public Type? DefaultScopeType { get; set; }
/// <summary>
/// Gets or sets the type corresponding to the default Token entity,
/// used by the non-generic token manager and the server/validation services.
/// </summary>
public Type? DefaultTokenType { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether additional filtering should be disabled,
/// so that the OpenIddict managers don't execute a second check to ensure the results

24
src/OpenIddict.Core/Resolvers/OpenIddictApplicationStoreResolver.cs

@ -1,24 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict.Core;
/// <summary>
/// Exposes a method allowing to resolve an application store.
/// </summary>
public sealed class OpenIddictApplicationStoreResolver : IOpenIddictApplicationStoreResolver
{
private readonly IServiceProvider _provider;
public OpenIddictApplicationStoreResolver(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <summary>
/// Returns an application store compatible with the specified application type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <returns>An <see cref="IOpenIddictApplicationStore{TApplication}"/>.</returns>
public IOpenIddictApplicationStore<TApplication> Get<TApplication>() where TApplication : class
=> _provider.GetService<IOpenIddictApplicationStore<TApplication>>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0228));
}

24
src/OpenIddict.Core/Resolvers/OpenIddictAuthorizationStoreResolver.cs

@ -1,24 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict.Core;
/// <summary>
/// Exposes a method allowing to resolve an authorization store.
/// </summary>
public sealed class OpenIddictAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver
{
private readonly IServiceProvider _provider;
public OpenIddictAuthorizationStoreResolver(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <summary>
/// Returns an authorization store compatible with the specified authorization type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <returns>An <see cref="IOpenIddictAuthorizationStore{TAuthorization}"/>.</returns>
public IOpenIddictAuthorizationStore<TAuthorization> Get<TAuthorization>() where TAuthorization : class
=> _provider.GetService<IOpenIddictAuthorizationStore<TAuthorization>>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0229));
}

24
src/OpenIddict.Core/Resolvers/OpenIddictScopeStoreResolver.cs

@ -1,24 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict.Core;
/// <summary>
/// Exposes a method allowing to resolve a scope store.
/// </summary>
public sealed class OpenIddictScopeStoreResolver : IOpenIddictScopeStoreResolver
{
private readonly IServiceProvider _provider;
public OpenIddictScopeStoreResolver(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <summary>
/// Returns a scope store compatible with the specified scope type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <returns>An <see cref="IOpenIddictScopeStore{TScope}"/>.</returns>
public IOpenIddictScopeStore<TScope> Get<TScope>() where TScope : class
=> _provider.GetService<IOpenIddictScopeStore<TScope>>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0230));
}

24
src/OpenIddict.Core/Resolvers/OpenIddictTokenStoreResolver.cs

@ -1,24 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict.Core;
/// <summary>
/// Exposes a method allowing to resolve a token store.
/// </summary>
public sealed class OpenIddictTokenStoreResolver : IOpenIddictTokenStoreResolver
{
private readonly IServiceProvider _provider;
public OpenIddictTokenStoreResolver(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <summary>
/// Returns a token store compatible with the specified token type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <returns>An <see cref="IOpenIddictTokenStore{TToken}"/>.</returns>
public IOpenIddictTokenStore<TToken> Get<TToken>() where TToken : class
=> _provider.GetService<IOpenIddictTokenStore<TToken>>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0231));
}

7
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkApplicationConfiguration.cs

@ -8,6 +8,7 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration;
using System.Diagnostics.CodeAnalysis;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework;
@ -20,7 +21,11 @@ namespace OpenIddict.EntityFramework;
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkApplicationConfiguration<TApplication, TAuthorization, TToken, TKey> : EntityTypeConfiguration<TApplication>
public sealed class OpenIddictEntityFrameworkApplicationConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : EntityTypeConfiguration<TApplication>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>

7
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkAuthorizationConfiguration.cs

@ -6,6 +6,7 @@
using System.ComponentModel;
using System.Data.Entity.ModelConfiguration;
using System.Diagnostics.CodeAnalysis;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework;
@ -18,7 +19,11 @@ namespace OpenIddict.EntityFramework;
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkAuthorizationConfiguration<TAuthorization, TApplication, TToken, TKey> : EntityTypeConfiguration<TAuthorization>
public sealed class OpenIddictEntityFrameworkAuthorizationConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : EntityTypeConfiguration<TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>

5
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkScopeConfiguration.cs

@ -8,6 +8,7 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration;
using System.Diagnostics.CodeAnalysis;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework;
@ -18,7 +19,9 @@ namespace OpenIddict.EntityFramework;
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkScopeConfiguration<TScope, TKey> : EntityTypeConfiguration<TScope>
public sealed class OpenIddictEntityFrameworkScopeConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : EntityTypeConfiguration<TScope>
where TScope : OpenIddictEntityFrameworkScope<TKey>
where TKey : notnull, IEquatable<TKey>
{

7
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkTokenConfiguration.cs

@ -8,6 +8,7 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration;
using System.Diagnostics.CodeAnalysis;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework;
@ -20,7 +21,11 @@ namespace OpenIddict.EntityFramework;
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkTokenConfiguration<TToken, TApplication, TAuthorization, TKey> : EntityTypeConfiguration<TToken>
public sealed class OpenIddictEntityFrameworkTokenConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : EntityTypeConfiguration<TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>

25
src/OpenIddict.EntityFramework/IOpenIddictEntityFrameworkContext.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.ComponentModel;
namespace OpenIddict.EntityFramework;
/// <summary>
/// Exposes the Entity Framework context used by the OpenIddict stores.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public interface IOpenIddictEntityFrameworkContext
{
/// <summary>
/// Gets the <see cref="DbContext"/>.
/// </summary>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the
/// asynchronous operation, whose result returns the <see cref="DbContext"/>.
/// </returns>
ValueTask<DbContext> GetDbContextAsync(CancellationToken cancellationToken);
}

63
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs

@ -5,6 +5,7 @@
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenIddict.Core;
using OpenIddict.EntityFramework;
@ -53,15 +54,21 @@ public sealed class OpenIddictEntityFrameworkBuilder
/// from the default OpenIddict Entity Framework 6.x entities.
/// </summary>
/// <returns>The <see cref="OpenIddictEntityFrameworkBuilder"/> instance.</returns>
public OpenIddictEntityFrameworkBuilder ReplaceDefaultEntities<TApplication, TAuthorization, TScope, TToken, TKey>()
public OpenIddictEntityFrameworkBuilder ReplaceDefaultEntities<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>()
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TScope : OpenIddictEntityFrameworkScope<TKey>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TKey : notnull, IEquatable<TKey>
{
// Note: unlike Entity Framework Core 1.x/2.x/3.x, Entity Framework 6.x
// always throws an exception when using generic types as entity types.
// Note: unlike Entity Framework Core, Entity Framework 6.x always
// throws an exception when using generic types as entity types.
//
// To ensure a better exception is thrown, a manual check is made here.
if (typeof(TApplication).IsGenericType || typeof(TAuthorization).IsGenericType ||
typeof(TScope).IsGenericType || typeof(TToken).IsGenericType)
@ -69,13 +76,23 @@ public sealed class OpenIddictEntityFrameworkBuilder
throw new InvalidOperationException(SR.GetResourceString(SR.ID0277));
}
Services.Configure<OpenIddictCoreOptions>(options =>
{
options.DefaultApplicationType = typeof(TApplication);
options.DefaultAuthorizationType = typeof(TAuthorization);
options.DefaultScopeType = typeof(TScope);
options.DefaultTokenType = typeof(TToken);
});
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictApplicationManager>(static provider =>
provider.GetRequiredService<OpenIddictApplicationManager<TApplication>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictAuthorizationManager>(static provider =>
provider.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeManager>(static provider =>
provider.GetRequiredService<OpenIddictScopeManager<TScope>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenManager>(static provider =>
provider.GetRequiredService<OpenIddictTokenManager<TToken>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictApplicationStore<TApplication>,
OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorization, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictAuthorizationStore<TAuthorization>,
OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplication, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeStore<TScope>,
OpenIddictEntityFrameworkScopeStore<TScope, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenStore<TToken>,
OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthorization, TKey>>());
return this;
}
@ -85,30 +102,12 @@ public sealed class OpenIddictEntityFrameworkBuilder
/// </summary>
/// <typeparam name="TContext">The type of the <see cref="DbContext"/> used by OpenIddict.</typeparam>
/// <returns>The <see cref="OpenIddictEntityFrameworkBuilder"/> instance.</returns>
public OpenIddictEntityFrameworkBuilder UseDbContext<TContext>()
where TContext : DbContext
=> UseDbContext(typeof(TContext));
/// <summary>
/// Configures the OpenIddict Entity Framework 6.x stores to use the specified database context type.
/// </summary>
/// <param name="type">The type of the <see cref="DbContext"/> used by OpenIddict.</param>
/// <returns>The <see cref="OpenIddictEntityFrameworkBuilder"/> instance.</returns>
public OpenIddictEntityFrameworkBuilder UseDbContext(Type type)
public OpenIddictEntityFrameworkBuilder UseDbContext<TContext>() where TContext : DbContext
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(DbContext).IsAssignableFrom(type))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.TryAddScoped(type);
Services.Replace(ServiceDescriptor.Scoped<
IOpenIddictEntityFrameworkContext, OpenIddictEntityFrameworkContext<TContext>>());
return Configure(options => options.DbContextType = type);
return this;
}
/// <inheritdoc/>

46
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkContext.cs

@ -0,0 +1,46 @@
/*
* 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.ComponentModel;
namespace OpenIddict.EntityFramework;
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictEntityFrameworkContext<TContext> : IOpenIddictEntityFrameworkContext
where TContext : DbContext
{
private readonly TContext? _context;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictEntityFrameworkContext{TContext}"/> class.
/// </summary>
public OpenIddictEntityFrameworkContext()
{
}
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictEntityFrameworkContext{TContext}"/> class.
/// </summary>
/// <param name="context">The Entity Framework Core context, if available.</param>
public OpenIddictEntityFrameworkContext(TContext? context) => _context = context;
/// <inheritdoc/>
public ValueTask<DbContext> GetDbContextAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return new(Task.FromCanceled<DbContext>(cancellationToken));
}
if (_context is not DbContext context)
{
return new(Task.FromException<DbContext>(new InvalidOperationException(SR.GetResourceString(SR.ID0470))));
}
return new(context);
}
}

22
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs

@ -39,20 +39,16 @@ public static class OpenIddictEntityFrameworkExtensions
.SetDefaultScopeEntity<OpenIddictEntityFrameworkScope>()
.SetDefaultTokenEntity<OpenIddictEntityFrameworkToken>();
builder.ReplaceApplicationStoreResolver<OpenIddictEntityFrameworkApplicationStoreResolver>()
.ReplaceAuthorizationStoreResolver<OpenIddictEntityFrameworkAuthorizationStoreResolver>()
.ReplaceScopeStoreResolver<OpenIddictEntityFrameworkScopeStoreResolver>()
.ReplaceTokenStoreResolver<OpenIddictEntityFrameworkTokenStoreResolver>();
builder.ReplaceApplicationStore<OpenIddictEntityFrameworkApplication, OpenIddictEntityFrameworkApplicationStore>()
.ReplaceAuthorizationStore<OpenIddictEntityFrameworkAuthorization, OpenIddictEntityFrameworkAuthorizationStore>()
.ReplaceScopeStore<OpenIddictEntityFrameworkScope, OpenIddictEntityFrameworkScopeStore>()
.ReplaceTokenStore<OpenIddictEntityFrameworkToken, OpenIddictEntityFrameworkTokenStore>();
builder.Services.TryAddSingleton<OpenIddictEntityFrameworkApplicationStoreResolver.TypeResolutionCache>();
builder.Services.TryAddSingleton<OpenIddictEntityFrameworkAuthorizationStoreResolver.TypeResolutionCache>();
builder.Services.TryAddSingleton<OpenIddictEntityFrameworkScopeStoreResolver.TypeResolutionCache>();
builder.Services.TryAddSingleton<OpenIddictEntityFrameworkTokenStoreResolver.TypeResolutionCache>();
builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkApplicationStore<,,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkAuthorizationStore<,,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkScopeStore<,,>));
builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkTokenStore<,,,,>));
// Note: a default context factory is always registered to make debugging easier when
// no database context type was configured by the user: the default implementation
// registered here is automatically replaced by the UseDbContext<TContext>() API.
builder.Services.TryAddScoped<IOpenIddictEntityFrameworkContext>(static provider =>
throw new InvalidOperationException(SR.GetResourceString(SR.ID0235)));
return new OpenIddictEntityFrameworkBuilder(builder.Services);
}

8
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs

@ -5,6 +5,7 @@
*/
using System.Data.Entity.Infrastructure;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.EntityFramework;
@ -40,7 +41,12 @@ public static class OpenIddictEntityFrameworkHelpers
/// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbModelBuilder UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey>(this DbModelBuilder builder)
public static DbModelBuilder UseOpenIddict<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this DbModelBuilder builder)
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TScope : OpenIddictEntityFrameworkScope<TKey>

6
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkOptions.cs

@ -12,10 +12,4 @@ namespace OpenIddict.EntityFramework;
/// </summary>
public sealed class OpenIddictEntityFrameworkOptions
{
/// <summary>
/// Gets or sets the concrete type of the <see cref="DbContext"/> used by the
/// OpenIddict Entity Framework 6.x stores. If this property is not populated,
/// an exception is thrown at runtime when trying to use the stores.
/// </summary>
public Type? DbContextType { get; set; }
}

72
src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolver.cs

@ -1,72 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFramework.Models;
using OpenIddict.Extensions;
namespace OpenIddict.EntityFramework;
/// <summary>
/// Exposes a method allowing to resolve an application store.
/// </summary>
public sealed class OpenIddictEntityFrameworkApplicationStoreResolver : IOpenIddictApplicationStoreResolver
{
private readonly TypeResolutionCache _cache;
private readonly IOptionsMonitor<OpenIddictEntityFrameworkOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictEntityFrameworkApplicationStoreResolver(
TypeResolutionCache cache,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options,
IServiceProvider provider)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <summary>
/// Returns an application store compatible with the specified application type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <returns>An <see cref="IOpenIddictApplicationStore{TApplication}"/>.</returns>
public IOpenIddictApplicationStore<TApplication> Get<TApplication>() where TApplication : class
{
var store = _provider.GetService<IOpenIddictApplicationStore<TApplication>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TApplication), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkApplication<,,>)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0234));
var context = _options.CurrentValue.DbContextType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0235));
return typeof(OpenIddictEntityFrameworkApplicationStore<,,,,>).MakeGenericType(
/* TApplication: */ key,
/* TAuthorization: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictApplicationStore<TApplication>) _provider.GetRequiredService(type);
}
// Note: Entity Framework resolvers are registered as scoped dependencies as their inner
// service provider must be able to resolve scoped services (typically, the store they return).
// To avoid having to declare a static type resolution cache, a special cache service is used
// here and registered as a singleton dependency so that its content persists beyond the scope.
public sealed class TypeResolutionCache : ConcurrentDictionary<Type, Type> { }
}

72
src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolver.cs

@ -1,72 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFramework.Models;
using OpenIddict.Extensions;
namespace OpenIddict.EntityFramework;
/// <summary>
/// Exposes a method allowing to resolve an authorization store.
/// </summary>
public sealed class OpenIddictEntityFrameworkAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver
{
private readonly TypeResolutionCache _cache;
private readonly IOptionsMonitor<OpenIddictEntityFrameworkOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictEntityFrameworkAuthorizationStoreResolver(
TypeResolutionCache cache,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options,
IServiceProvider provider)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <summary>
/// Returns an authorization store compatible with the specified authorization type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <returns>An <see cref="IOpenIddictAuthorizationStore{TAuthorization}"/>.</returns>
public IOpenIddictAuthorizationStore<TAuthorization> Get<TAuthorization>() where TAuthorization : class
{
var store = _provider.GetService<IOpenIddictAuthorizationStore<TAuthorization>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TAuthorization), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkAuthorization<,,>)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0236));
var context = _options.CurrentValue.DbContextType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0235));
return typeof(OpenIddictEntityFrameworkAuthorizationStore<,,,,>).MakeGenericType(
/* TAuthorization: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictAuthorizationStore<TAuthorization>) _provider.GetRequiredService(type);
}
// Note: Entity Framework resolvers are registered as scoped dependencies as their inner
// service provider must be able to resolve scoped services (typically, the store they return).
// To avoid having to declare a static type resolution cache, a special cache service is used
// here and registered as a singleton dependency so that its content persists beyond the scope.
public sealed class TypeResolutionCache : ConcurrentDictionary<Type, Type> { }
}

70
src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkScopeStoreResolver.cs

@ -1,70 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFramework.Models;
using OpenIddict.Extensions;
namespace OpenIddict.EntityFramework;
/// <summary>
/// Exposes a method allowing to resolve a scope store.
/// </summary>
public sealed class OpenIddictEntityFrameworkScopeStoreResolver : IOpenIddictScopeStoreResolver
{
private readonly TypeResolutionCache _cache;
private readonly IOptionsMonitor<OpenIddictEntityFrameworkOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictEntityFrameworkScopeStoreResolver(
TypeResolutionCache cache,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options,
IServiceProvider provider)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <summary>
/// Returns a scope store compatible with the specified scope type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <returns>An <see cref="IOpenIddictScopeStore{TScope}"/>.</returns>
public IOpenIddictScopeStore<TScope> Get<TScope>() where TScope : class
{
var store = _provider.GetService<IOpenIddictScopeStore<TScope>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TScope), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkScope<>)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0237));
var context = _options.CurrentValue.DbContextType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0235));
return typeof(OpenIddictEntityFrameworkScopeStore<,,>).MakeGenericType(
/* TScope: */ key,
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictScopeStore<TScope>) _provider.GetRequiredService(type);
}
// Note: Entity Framework resolvers are registered as scoped dependencies as their inner
// service provider must be able to resolve scoped services (typically, the store they return).
// To avoid having to declare a static type resolution cache, a special cache service is used
// here and registered as a singleton dependency so that its content persists beyond the scope.
public sealed class TypeResolutionCache : ConcurrentDictionary<Type, Type> { }
}

72
src/OpenIddict.EntityFramework/Resolvers/OpenIddictEntityFrameworkTokenStoreResolver.cs

@ -1,72 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFramework.Models;
using OpenIddict.Extensions;
namespace OpenIddict.EntityFramework;
/// <summary>
/// Exposes a method allowing to resolve a token store.
/// </summary>
public sealed class OpenIddictEntityFrameworkTokenStoreResolver : IOpenIddictTokenStoreResolver
{
private readonly TypeResolutionCache _cache;
private readonly IOptionsMonitor<OpenIddictEntityFrameworkOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictEntityFrameworkTokenStoreResolver(
TypeResolutionCache cache,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options,
IServiceProvider provider)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <summary>
/// Returns a token store compatible with the specified token type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <returns>An <see cref="IOpenIddictTokenStore{TToken}"/>.</returns>
public IOpenIddictTokenStore<TToken> Get<TToken>() where TToken : class
{
var store = _provider.GetService<IOpenIddictTokenStore<TToken>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TToken), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkToken<,,>)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0238));
var context = _options.CurrentValue.DbContextType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0235));
return typeof(OpenIddictEntityFrameworkTokenStore<,,,,>).MakeGenericType(
/* TToken: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TAuthorization: */ root.GenericTypeArguments[2],
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictTokenStore<TToken>) _provider.GetRequiredService(type);
}
// Note: Entity Framework resolvers are registered as scoped dependencies as their inner
// service provider must be able to resolve scoped services (typically, the store they return).
// To avoid having to declare a static type resolution cache, a special cache service is used
// here and registered as a singleton dependency so that its content persists beyond the scope.
public sealed class TypeResolutionCache : ConcurrentDictionary<Type, Type> { }
}

155
src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs

@ -25,16 +25,14 @@ namespace OpenIddict.EntityFramework;
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictEntityFrameworkApplicationStore<TContext> :
public class OpenIddictEntityFrameworkApplicationStore :
OpenIddictEntityFrameworkApplicationStore<OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkToken, TContext, string>
where TContext : DbContext
OpenIddictEntityFrameworkToken, string>
{
public OpenIddictEntityFrameworkApplicationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
: base(cache, context, options)
{
@ -47,18 +45,20 @@ public class OpenIddictEntityFrameworkApplicationStore<TContext> :
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorization, TToken, TContext, TKey> : IOpenIddictApplicationStore<TApplication>
public class OpenIddictEntityFrameworkApplicationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TContext : DbContext
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkApplicationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
@ -74,31 +74,20 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected TContext Context { get; }
protected IOpenIddictEntityFrameworkContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
=> await Applications.LongCountAsync(cancellationToken);
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TApplication>().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TResult>(Func<IQueryable<TApplication>, IQueryable<TResult>> query, CancellationToken cancellationToken)
@ -108,7 +97,9 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
throw new ArgumentNullException(nameof(query));
}
return await query(Applications).LongCountAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TApplication>()).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
@ -119,9 +110,11 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
throw new ArgumentNullException(nameof(application));
}
Applications.Add(application);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TApplication>().Add(application);
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
@ -132,13 +125,15 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
throw new ArgumentNullException(nameof(application));
}
var context = await Context.GetDbContextAsync(cancellationToken);
Task<List<TAuthorization>> ListAuthorizationsAsync()
=> (from authorization in Authorizations.Include(authorization => authorization.Tokens)
=> (from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Tokens)
where authorization.Application!.Id!.Equals(application.Id)
select authorization).ToListAsync(cancellationToken);
Task<List<TToken>> ListTokensAsync()
=> (from token in Tokens
=> (from token in context.Set<TToken>()
where token.Authorization == null
where token.Application!.Id!.Equals(application.Id)
select token).ToListAsync(cancellationToken);
@ -146,7 +141,7 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
using var transaction = Context.CreateTransaction(IsolationLevel.Serializable);
using var transaction = context.CreateTransaction(IsolationLevel.Serializable);
// Remove all the authorizations associated with the application and
// the tokens attached to these implicit or explicit authorizations.
@ -155,40 +150,40 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
{
foreach (var token in authorization.Tokens)
{
Tokens.Remove(token);
context.Set<TToken>().Remove(token);
}
Authorizations.Remove(authorization);
context.Set<TAuthorization>().Remove(authorization);
}
// Remove all the tokens associated with the application.
var tokens = await ListTokensAsync();
foreach (var token in tokens)
{
Tokens.Remove(token);
context.Set<TToken>().Remove(token);
}
Applications.Remove(application);
context.Set<TApplication>().Remove(application);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(application).State = EntityState.Unchanged;
context.Entry(application).State = EntityState.Unchanged;
foreach (var authorization in authorizations)
{
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
}
foreach (var token in tokens)
{
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
}
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -196,45 +191,48 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
}
/// <inheritdoc/>
public virtual ValueTask<TApplication?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TApplication?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
return GetTrackedEntity() is TApplication application ? new(application) : new(QueryAsync());
var context = await Context.GetDbContextAsync(cancellationToken);
return GetTrackedEntity() is TApplication application ? application : await QueryAsync();
TApplication? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TApplication>()
(from entry in context.ChangeTracker.Entries<TApplication>()
where string.Equals(entry.Entity.ClientId, identifier, StringComparison.Ordinal)
select entry.Entity).FirstOrDefault();
Task<TApplication?> QueryAsync() =>
(from application in Applications
(from application in context.Set<TApplication>()
where application.ClientId == identifier
select application).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<TApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return GetTrackedEntity() is TApplication application ? new(application) : new(QueryAsync());
return GetTrackedEntity() is TApplication application ? application : await QueryAsync();
TApplication? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TApplication>()
(from entry in context.ChangeTracker.Entries<TApplication>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TApplication?> QueryAsync() =>
(from application in Applications
(from application in context.Set<TApplication>()
where application.Id!.Equals(key)
select application).FirstOrDefaultAsync(cancellationToken);
}
@ -258,7 +256,9 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
async IAsyncEnumerable<TApplication> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var applications = (from application in Applications
var context = await Context.GetDbContextAsync(cancellationToken);
var applications = (from application in context.Set<TApplication>()
where application.PostLogoutRedirectUris!.Contains(uri)
select application).AsAsyncEnumerable(cancellationToken);
@ -292,7 +292,9 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
async IAsyncEnumerable<TApplication> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var applications = (from application in Applications
var context = await Context.GetDbContextAsync(cancellationToken);
var applications = (from application in context.Set<TApplication>()
where application.RedirectUris!.Contains(uri)
select application).AsAsyncEnumerable(cancellationToken);
@ -328,7 +330,9 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
throw new ArgumentNullException(nameof(query));
}
return await query(Applications, state).FirstOrDefaultAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TApplication>(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
@ -721,10 +725,12 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TApplication> ListAsync(
int? count, int? offset, CancellationToken cancellationToken)
public virtual async IAsyncEnumerable<TApplication> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var query = Applications.OrderBy(application => application.Id!).AsQueryable();
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TApplication> query = context.Set<TApplication>().OrderBy(application => application.Id!);
if (offset.HasValue)
{
@ -736,7 +742,10 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
query = query.Take(count.Value);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var application in query.AsAsyncEnumerable(cancellationToken))
{
yield return application;
}
}
/// <inheritdoc/>
@ -749,7 +758,17 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
throw new ArgumentNullException(nameof(query));
}
return query(Applications, state).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var application in query(context.Set<TApplication>(), state).AsAsyncEnumerable(cancellationToken))
{
yield return application;
}
}
}
/// <inheritdoc/>
@ -878,7 +897,7 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
throw new ArgumentNullException(nameof(application));
}
application.JsonWebKeySet = set is not null ? JsonSerializer.Serialize(set) : null;
application.JsonWebKeySet = set is not null ? JsonSerializer.Serialize(set, OpenIddictSerializer.Default.JsonWebKeySet) : null;
return default;
}
@ -1119,23 +1138,25 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
throw new ArgumentNullException(nameof(application));
}
Applications.Attach(application);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TApplication>().Attach(application);
// Generate a new concurrency token and attach it
// to the application before persisting the changes.
application.ConcurrencyToken = Guid.NewGuid().ToString();
Context.Entry(application).State = EntityState.Modified;
context.Entry(application).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(application).State = EntityState.Unchanged;
context.Entry(application).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
}
@ -1153,7 +1174,11 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
return default;
}
return (TKey?) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
return (TKey?) GetConverter().ConvertFromInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
/// <summary>
@ -1168,6 +1193,10 @@ public class OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorizat
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
return GetConverter().ConvertToInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
}

223
src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs

@ -8,6 +8,7 @@ using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
@ -23,16 +24,14 @@ namespace OpenIddict.EntityFramework;
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictEntityFrameworkAuthorizationStore<TContext> :
public class OpenIddictEntityFrameworkAuthorizationStore :
OpenIddictEntityFrameworkAuthorizationStore<OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkToken, TContext, string>
where TContext : DbContext
OpenIddictEntityFrameworkToken, string>
{
public OpenIddictEntityFrameworkAuthorizationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
: base(cache, context, options)
{
@ -45,18 +44,20 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TContext> :
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplication, TToken, TContext, TKey> : IOpenIddictAuthorizationStore<TAuthorization>
public class OpenIddictEntityFrameworkAuthorizationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TContext : DbContext
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkAuthorizationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
@ -72,31 +73,20 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected TContext Context { get; }
protected IOpenIddictEntityFrameworkContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
=> await Authorizations.LongCountAsync(cancellationToken);
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TAuthorization>().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TResult>(Func<IQueryable<TAuthorization>, IQueryable<TResult>> query, CancellationToken cancellationToken)
@ -106,7 +96,9 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentNullException(nameof(query));
}
return await query(Authorizations).LongCountAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TAuthorization>()).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
@ -117,9 +109,11 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentNullException(nameof(authorization));
}
Authorizations.Add(authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
context.Set<TAuthorization>().Add(authorization);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
@ -130,39 +124,41 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentNullException(nameof(authorization));
}
var context = await Context.GetDbContextAsync(cancellationToken);
Task<List<TToken>> ListTokensAsync()
=> (from token in Tokens
=> (from token in context.Set<TToken>()
where token.Authorization!.Id!.Equals(authorization.Id)
select token).ToListAsync(cancellationToken);
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
using var transaction = Context.CreateTransaction(IsolationLevel.Serializable);
using var transaction = context.CreateTransaction(IsolationLevel.Serializable);
// Remove all the tokens associated with the authorization.
var tokens = await ListTokensAsync();
foreach (var token in tokens)
{
Tokens.Remove(token);
context.Set<TToken>().Remove(token);
}
Authorizations.Remove(authorization);
context.Set<TAuthorization>().Remove(authorization);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
foreach (var token in tokens)
{
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
}
throw new ConcurrencyException(SR.GetResourceString(SR.ID0241), exception);
@ -175,7 +171,9 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
string? status, string? type,
ImmutableArray<string>? scopes, [EnumeratorCancellation] CancellationToken cancellationToken)
{
IQueryable<TAuthorization> query = Authorizations.Include(authorization => authorization.Application);
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TAuthorization> query = context.Set<TAuthorization>().Include(authorization => authorization.Application);
if (!string.IsNullOrEmpty(subject))
{
@ -211,56 +209,76 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TAuthorization> FindByApplicationIdAsync(
string identifier, CancellationToken cancellationToken)
public virtual IAsyncEnumerable<TAuthorization> FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var key = ConvertIdentifierFromString(identifier);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TAuthorization> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return (from authorization in Authorizations.Include(authorization => authorization.Application)
where authorization.Application!.Id!.Equals(key)
select authorization).AsAsyncEnumerable(cancellationToken);
await foreach (var authorization in
(from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Application)
where authorization.Application!.Id!.Equals(key)
select authorization).AsAsyncEnumerable(cancellationToken))
{
yield return authorization;
}
}
}
/// <inheritdoc/>
public virtual ValueTask<TAuthorization?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TAuthorization?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return GetTrackedEntity() is TAuthorization authorization ? new(authorization) : new(QueryAsync());
return GetTrackedEntity() is TAuthorization authorization ? authorization : await QueryAsync();
TAuthorization? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TAuthorization>()
(from entry in context.ChangeTracker.Entries<TAuthorization>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TAuthorization?> QueryAsync() =>
(from authorization in Authorizations.Include(authorization => authorization.Application)
(from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Application)
where authorization.Id!.Equals(key)
select authorization).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TAuthorization> FindBySubjectAsync(
string subject, CancellationToken cancellationToken)
public virtual IAsyncEnumerable<TAuthorization> FindBySubjectAsync(string subject, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject));
}
return (from authorization in Authorizations.Include(authorization => authorization.Application)
where authorization.Subject == subject
select authorization).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TAuthorization> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var authorization in
(from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Application)
where authorization.Subject == subject
select authorization).AsAsyncEnumerable(cancellationToken))
{
yield return authorization;
}
}
}
/// <inheritdoc/>
@ -271,10 +289,12 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentNullException(nameof(authorization));
}
var context = await Context.GetDbContextAsync(cancellationToken);
// If the application is not attached to the authorization, try to load it manually.
if (authorization.Application is null)
{
var reference = Context.Entry(authorization).Reference(entry => entry.Application);
var reference = context.Entry(authorization).Reference(entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
@ -301,8 +321,10 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentNullException(nameof(query));
}
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(
Authorizations.Include(authorization => authorization.Application), state).FirstOrDefaultAsync(cancellationToken);
context.Set<TAuthorization>().Include(authorization => authorization.Application), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
@ -457,11 +479,14 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TAuthorization> ListAsync(int? count, int? offset, CancellationToken cancellationToken)
public virtual async IAsyncEnumerable<TAuthorization> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var query = Authorizations.Include(authorization => authorization.Application)
.OrderBy(authorization => authorization.Id!)
.AsQueryable();
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TAuthorization> query = context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
.OrderBy(authorization => authorization.Id!);
if (offset.HasValue)
{
@ -473,7 +498,10 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
query = query.Take(count.Value);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var authorization in query.AsAsyncEnumerable(cancellationToken))
{
yield return authorization;
}
}
/// <inheritdoc/>
@ -486,12 +514,26 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentNullException(nameof(query));
}
return query(Authorizations.Include(authorization => authorization.Application), state).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var authorization in query(context
.Set<TAuthorization>()
.Include(authorization => authorization.Application), state).AsAsyncEnumerable(cancellationToken))
{
yield return authorization;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
// Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
// To work around this limitation, entities are manually listed and deleted using a batch logic.
@ -510,7 +552,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
// after it was retrieved from the database, the following logic is executed in
// a repeatable read transaction, that will put a lock on the retrieved entries
// and thus prevent them from being concurrently modified outside this block.
using var transaction = Context.CreateTransaction(IsolationLevel.RepeatableRead);
using var transaction = context.CreateTransaction(IsolationLevel.RepeatableRead);
// Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable
// to create a SQL query with an expression calling DateTimeOffset.UtcDateTime.
@ -519,7 +561,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
var date = threshold.UtcDateTime;
var authorizations =
await (from authorization in Authorizations.Include(authorization => authorization.Tokens)
await (from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Tokens)
where authorization.CreationDate < date
where authorization.Status != Statuses.Valid || authorization.Type == AuthorizationTypes.AdHoc
where !authorization.Tokens.Any()
@ -535,11 +577,11 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
// from the database since the transaction level is deliberately limited to
// repeatable read instead of serializable for performance reasons). In this
// case, the operation will fail, which is considered an acceptable risk.
Authorizations.RemoveRange(authorizations);
context.Set<TAuthorization>().RemoveRange(authorizations);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
@ -565,7 +607,9 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
/// <inheritdoc/>
public virtual async ValueTask<long> RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken)
{
IQueryable<TAuthorization> query = Authorizations.Include(authorization => authorization.Application);
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TAuthorization> query = context.Set<TAuthorization>().Include(authorization => authorization.Application);
if (!string.IsNullOrEmpty(subject))
{
@ -599,13 +643,13 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -632,13 +676,15 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
List<Exception>? exceptions = null;
var result = 0L;
foreach (var authorization in await (from authorization in Authorizations.Include(authorization => authorization.Application)
foreach (var authorization in await (from authorization in context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
where authorization.Application!.Id!.Equals(key)
select authorization).ToListAsync(cancellationToken))
{
@ -646,13 +692,13 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -679,11 +725,14 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
}
var context = await Context.GetDbContextAsync(cancellationToken);
List<Exception>? exceptions = null;
var result = 0L;
foreach (var authorization in await (from authorization in Authorizations.Include(authorization => authorization.Application)
foreach (var authorization in await (from authorization in context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
where authorization.Subject == subject
select authorization).ToListAsync(cancellationToken))
{
@ -691,13 +740,13 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -725,9 +774,11 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentNullException(nameof(authorization));
}
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier))
{
authorization.Application = await Applications.FindAsync(
authorization.Application = await context.Set<TApplication>().FindAsync(
cancellationToken, ConvertIdentifierFromString(identifier)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0244));
}
@ -737,7 +788,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
// If the application is not attached to the authorization, try to load it manually.
if (authorization.Application is null)
{
var reference = Context.Entry(authorization).Reference(entry => entry.Application);
var reference = context.Entry(authorization).Reference(entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
@ -888,23 +939,25 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
throw new ArgumentNullException(nameof(authorization));
}
Authorizations.Attach(authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TAuthorization>().Attach(authorization);
// Generate a new concurrency token and attach it
// to the authorization before persisting the changes.
authorization.ConcurrencyToken = Guid.NewGuid().ToString();
Context.Entry(authorization).State = EntityState.Modified;
context.Entry(authorization).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0241), exception);
}
@ -922,7 +975,11 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
return default;
}
return (TKey?) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
return (TKey?) GetConverter().ConvertFromInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
/// <summary>
@ -937,6 +994,10 @@ public class OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplic
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
return GetConverter().ConvertToInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
}

145
src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs

@ -7,6 +7,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data.Entity.Infrastructure;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
@ -22,14 +23,12 @@ namespace OpenIddict.EntityFramework;
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictEntityFrameworkScopeStore<TContext> :
OpenIddictEntityFrameworkScopeStore<OpenIddictEntityFrameworkScope, TContext, string>
where TContext : DbContext
public class OpenIddictEntityFrameworkScopeStore :
OpenIddictEntityFrameworkScopeStore<OpenIddictEntityFrameworkScope, string>
{
public OpenIddictEntityFrameworkScopeStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
: base(cache, context, options)
{
@ -40,16 +39,16 @@ public class OpenIddictEntityFrameworkScopeStore<TContext> :
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpenIddictScopeStore<TScope>
public class OpenIddictEntityFrameworkScopeStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictScopeStore<TScope>
where TScope : OpenIddictEntityFrameworkScope<TKey>
where TContext : DbContext
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkScopeStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
@ -65,21 +64,20 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected TContext Context { get; }
protected IOpenIddictEntityFrameworkContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TScope"/> entity.
/// </summary>
private DbSet<TScope> Scopes => Context.Set<TScope>();
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
=> await Scopes.LongCountAsync(cancellationToken);
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TScope>().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TResult>(Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken)
@ -89,7 +87,9 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
throw new ArgumentNullException(nameof(query));
}
return await query(Scopes).LongCountAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TScope>()).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
@ -100,9 +100,11 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
throw new ArgumentNullException(nameof(scope));
}
Scopes.Add(scope);
var context = await Context.GetDbContextAsync(cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
context.Set<TScope>().Add(scope);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
@ -113,80 +115,95 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
throw new ArgumentNullException(nameof(scope));
}
Scopes.Remove(scope);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TScope>().Remove(scope);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(scope).State = EntityState.Unchanged;
context.Entry(scope).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0245), exception);
}
}
/// <inheritdoc/>
public virtual ValueTask<TScope?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TScope?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return GetTrackedEntity() is TScope scope ? new(scope) : new(QueryAsync());
return GetTrackedEntity() is TScope scope ? scope : await QueryAsync();
TScope? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TScope>()
(from entry in context.ChangeTracker.Entries<TScope>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TScope?> QueryAsync() =>
(from scope in Scopes
(from scope in context.Set<TScope>()
where scope.Id!.Equals(key)
select scope).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<TScope?> FindByNameAsync(string name, CancellationToken cancellationToken)
public virtual async ValueTask<TScope?> FindByNameAsync(string name, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name));
}
return GetTrackedEntity() is TScope scope ? new(scope) : new(QueryAsync());
var context = await Context.GetDbContextAsync(cancellationToken);
return GetTrackedEntity() is TScope scope ? scope : await QueryAsync();
TScope? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TScope>()
(from entry in context.ChangeTracker.Entries<TScope>()
where string.Equals(entry.Entity.Name, name, StringComparison.Ordinal)
select entry.Entity).FirstOrDefault();
Task<TScope?> QueryAsync() =>
(from scope in Scopes
(from scope in context.Set<TScope>()
where scope.Name == name
select scope).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TScope> FindByNamesAsync(
ImmutableArray<string> names, CancellationToken cancellationToken)
public virtual IAsyncEnumerable<TScope> FindByNamesAsync(ImmutableArray<string> names, CancellationToken cancellationToken)
{
if (names.Any(string.IsNullOrEmpty))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names));
}
// Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure
// ImmutableArray.Contains() (which is not fully supported by Entity Framework 6.x) is not used instead.
return (from scope in Scopes
where Enumerable.Contains(names, scope.Name)
select scope).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TScope> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
// Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure
// ImmutableArray.Contains() (which is not fully supported by Entity Framework 6.x) is not used instead.
await foreach (var scope in
(from scope in context.Set<TScope>()
where Enumerable.Contains(names, scope.Name)
select scope).AsAsyncEnumerable(cancellationToken))
{
yield return scope;
}
}
}
/// <inheritdoc/>
@ -208,7 +225,9 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
async IAsyncEnumerable<TScope> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var scopes = (from scope in Scopes
var context = await Context.GetDbContextAsync(cancellationToken);
var scopes = (from scope in context.Set<TScope>()
where scope.Resources!.Contains(resource)
select scope).AsAsyncEnumerable(cancellationToken);
@ -233,7 +252,9 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
throw new ArgumentNullException(nameof(query));
}
return await query(Scopes, state).FirstOrDefaultAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TScope>(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
@ -454,9 +475,12 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TScope> ListAsync(int? count, int? offset, CancellationToken cancellationToken)
public virtual async IAsyncEnumerable<TScope> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var query = Scopes.OrderBy(scope => scope.Id!).AsQueryable();
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TScope> query = context.Set<TScope>().OrderBy(scope => scope.Id!);
if (offset.HasValue)
{
@ -468,7 +492,10 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
query = query.Take(count.Value);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var scope in query.AsAsyncEnumerable(cancellationToken))
{
yield return scope;
}
}
/// <inheritdoc/>
@ -481,7 +508,17 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
throw new ArgumentNullException(nameof(query));
}
return query(Scopes, state).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var scope in query(context.Set<TScope>(), state).AsAsyncEnumerable(cancellationToken))
{
yield return scope;
}
}
}
/// <inheritdoc/>
@ -685,23 +722,25 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
throw new ArgumentNullException(nameof(scope));
}
Scopes.Attach(scope);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TScope>().Attach(scope);
// Generate a new concurrency token and attach it
// to the scope before persisting the changes.
scope.ConcurrencyToken = Guid.NewGuid().ToString();
Context.Entry(scope).State = EntityState.Modified;
context.Entry(scope).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(scope).State = EntityState.Unchanged;
context.Entry(scope).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0245), exception);
}
@ -719,7 +758,11 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
return default;
}
return (TKey?) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
return (TKey?) GetConverter().ConvertFromInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
/// <summary>
@ -734,6 +777,10 @@ public class OpenIddictEntityFrameworkScopeStore<TScope, TContext, TKey> : IOpen
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
return GetConverter().ConvertToInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
}

278
src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs

@ -8,6 +8,8 @@ using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
@ -22,16 +24,14 @@ namespace OpenIddict.EntityFramework;
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictEntityFrameworkTokenStore<TContext> :
public class OpenIddictEntityFrameworkTokenStore :
OpenIddictEntityFrameworkTokenStore<OpenIddictEntityFrameworkToken,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkAuthorization, TContext, string>
where TContext : DbContext
OpenIddictEntityFrameworkAuthorization, string>
{
public OpenIddictEntityFrameworkTokenStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
: base(cache, context, options)
{
@ -44,18 +44,20 @@ public class OpenIddictEntityFrameworkTokenStore<TContext> :
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthorization, TContext, TKey> : IOpenIddictTokenStore<TToken>
public class OpenIddictEntityFrameworkTokenStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TContext : DbContext
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkTokenStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkContext context,
IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
@ -71,31 +73,20 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected TContext Context { get; }
protected IOpenIddictEntityFrameworkContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
=> await Tokens.LongCountAsync(cancellationToken);
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TToken>().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TResult>(Func<IQueryable<TToken>, IQueryable<TResult>> query, CancellationToken cancellationToken)
@ -105,7 +96,9 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(query));
}
return await query(Tokens).LongCountAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TToken>()).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
@ -116,9 +109,11 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(token));
}
Tokens.Add(token);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TToken>().Add(token);
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
@ -129,28 +124,34 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(token));
}
Tokens.Remove(token);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TToken>().Remove(token);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0247), exception);
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TToken> FindAsync(
public virtual async IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client,
string? status, string? type, CancellationToken cancellationToken)
string? status, string? type, [EnumeratorCancellation] CancellationToken cancellationToken)
{
IQueryable<TToken> query = Tokens.Include(token => token.Application).Include(token => token.Authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TToken> query = context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization);
if (!string.IsNullOrEmpty(subject))
{
@ -174,7 +175,10 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
query = query.Where(token => token.Type == type);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var token in query.AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
/// <inheritdoc/>
@ -185,11 +189,21 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var key = ConvertIdentifierFromString(identifier);
return ExecuteAsync(cancellationToken);
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Application!.Id!.Equals(key)
select token).AsAsyncEnumerable(cancellationToken);
async IAsyncEnumerable<TToken> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
await foreach (var token in
(from token in context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization)
where token.Application!.Id!.Equals(key)
select token).AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
}
/// <inheritdoc/>
@ -200,53 +214,66 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var key = ConvertIdentifierFromString(identifier);
return ExecuteAsync(cancellationToken);
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Authorization!.Id!.Equals(key)
select token).AsAsyncEnumerable(cancellationToken);
async IAsyncEnumerable<TToken> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
await foreach (var token in
(from token in context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization)
where token.Authorization!.Id!.Equals(key)
select token).AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
}
/// <inheritdoc/>
public virtual ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return GetTrackedEntity() is TToken token ? new(token) : new(QueryAsync());
return GetTrackedEntity() is TToken token ? token : await QueryAsync();
TToken? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TToken>()
(from entry in context.ChangeTracker.Entries<TToken>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TToken?> QueryAsync() =>
(from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
(from token in context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization)
where token.Id!.Equals(key)
select token).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<TToken?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TToken?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
return GetTrackedEntity() is TToken token ? new(token) : new(QueryAsync());
var context = await Context.GetDbContextAsync(cancellationToken);
return GetTrackedEntity() is TToken token ? token : await QueryAsync();
TToken? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TToken>()
(from entry in context.ChangeTracker.Entries<TToken>()
where string.Equals(entry.Entity.ReferenceId, identifier, StringComparison.Ordinal)
select entry.Entity).FirstOrDefault();
Task<TToken?> QueryAsync() =>
(from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
(from token in context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization)
where token.ReferenceId == identifier
select token).FirstOrDefaultAsync(cancellationToken);
}
@ -259,9 +286,20 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject));
}
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Subject == subject
select token).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TToken> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var token in
(from token in context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization)
where token.Subject == subject
select token).AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
}
/// <inheritdoc/>
@ -272,10 +310,12 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(token));
}
var context = await Context.GetDbContextAsync(cancellationToken);
// If the application is not attached to the token, try to load it manually.
if (token.Application is null)
{
var reference = Context.Entry(token).Reference(entry => entry.Application);
var reference = context.Entry(token).Reference(entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
@ -302,9 +342,12 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(query));
}
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(
Tokens.Include(token => token.Application)
.Include(token => token.Authorization), state).FirstOrDefaultAsync(cancellationToken);
context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
@ -315,10 +358,12 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(token));
}
var context = await Context.GetDbContextAsync(cancellationToken);
// If the authorization is not attached to the token, try to load it manually.
if (token.Authorization is null)
{
var reference = Context.Entry(token).Reference(entry => entry.Authorization);
var reference = context.Entry(token).Reference(entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
@ -500,12 +545,15 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TToken> ListAsync(int? count, int? offset, CancellationToken cancellationToken)
public virtual async IAsyncEnumerable<TToken> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var query = Tokens.Include(token => token.Application)
.Include(token => token.Authorization)
.OrderBy(token => token.Id!)
.AsQueryable();
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TToken> query = context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.OrderBy(token => token.Id!);
if (offset.HasValue)
{
@ -517,7 +565,10 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
query = query.Take(count.Value);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var token in query.AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
/// <inheritdoc/>
@ -530,14 +581,27 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(query));
}
return query(
Tokens.Include(token => token.Application)
.Include(token => token.Authorization), state).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var token in query(
context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization), state).AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
// Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
// To work around this limitation, entities are manually listed and deleted using a batch logic.
@ -556,7 +620,7 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
// after it was retrieved from the database, the following logic is executed in
// a repeatable read transaction, that will put a lock on the retrieved entries
// and thus prevent them from being concurrently modified outside this block.
using var transaction = Context.CreateTransaction(IsolationLevel.RepeatableRead);
using var transaction = context.CreateTransaction(IsolationLevel.RepeatableRead);
// Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable
// to create a SQL query with an expression calling DateTimeOffset.UtcDateTime.
@ -565,7 +629,7 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
var date = threshold.UtcDateTime;
var tokens = await
(from token in Tokens
(from token in context.Set<TToken>()
where token.CreationDate < date
where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) ||
(token.Authorization != null && token.Authorization.Status != Statuses.Valid) ||
@ -578,11 +642,11 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
break;
}
Tokens.RemoveRange(tokens);
context.Set<TToken>().RemoveRange(tokens);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
@ -608,7 +672,9 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
/// <inheritdoc/>
public virtual async ValueTask<long> RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken)
{
IQueryable<TToken> query = Tokens.Include(token => token.Application).Include(token => token.Authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TToken> query = context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization);
if (!string.IsNullOrEmpty(subject))
{
@ -642,13 +708,13 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -675,13 +741,16 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
List<Exception>? exceptions = null;
var result = 0L;
foreach (var token in await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
foreach (var token in await (from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
where token.Application!.Id!.Equals(key)
select token).ToListAsync(cancellationToken))
{
@ -689,13 +758,13 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -722,13 +791,16 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
List<Exception>? exceptions = null;
var result = 0L;
foreach (var token in await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
foreach (var token in await (from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
where token.Authorization!.Id!.Equals(key)
select token).ToListAsync(cancellationToken))
{
@ -736,13 +808,13 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -769,11 +841,15 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
}
var context = await Context.GetDbContextAsync(cancellationToken);
List<Exception>? exceptions = null;
var result = 0L;
foreach (var token in await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
foreach (var token in await (from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
where token.Subject == subject
select token).ToListAsync(cancellationToken))
{
@ -781,13 +857,13 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -814,9 +890,11 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(token));
}
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier))
{
token.Application = await Applications.FindAsync(
token.Application = await context.Set<TApplication>().FindAsync(
cancellationToken, ConvertIdentifierFromString(identifier)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0250));
}
@ -826,7 +904,7 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
// If the application is not attached to the token, try to load it manually.
if (token.Application is null)
{
var reference = Context.Entry(token).Reference(entry => entry.Application);
var reference = context.Entry(token).Reference(entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
@ -847,9 +925,11 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(token));
}
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier))
{
token.Authorization = await Authorizations.FindAsync(
token.Authorization = await context.Set<TAuthorization>().FindAsync(
cancellationToken, ConvertIdentifierFromString(identifier)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0251));
}
@ -859,7 +939,7 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
// If the authorization is not attached to the token, try to load it manually.
if (token.Authorization is null)
{
var reference = Context.Entry(token).Reference(entry => entry.Authorization);
var reference = context.Entry(token).Reference(entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
@ -1023,23 +1103,25 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
throw new ArgumentNullException(nameof(token));
}
Tokens.Attach(token);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Set<TToken>().Attach(token);
// Generate a new concurrency token and attach it
// to the token before persisting the changes.
token.ConcurrencyToken = Guid.NewGuid().ToString();
Context.Entry(token).State = EntityState.Modified;
context.Entry(token).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0247), exception);
}
@ -1057,7 +1139,11 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
return default;
}
return (TKey?) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
return (TKey?) GetConverter().ConvertFromInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
/// <summary>
@ -1072,6 +1158,10 @@ public class OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthoriz
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
return GetConverter().ConvertToInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
}

7
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreApplicationConfiguration.cs

@ -5,6 +5,7 @@
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OpenIddict.EntityFrameworkCore.Models;
@ -18,7 +19,11 @@ namespace OpenIddict.EntityFrameworkCore;
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkCoreApplicationConfiguration<TApplication, TAuthorization, TToken, TKey> : IEntityTypeConfiguration<TApplication>
public sealed class OpenIddictEntityFrameworkCoreApplicationConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IEntityTypeConfiguration<TApplication>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>

7
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreAuthorizationConfiguration.cs

@ -5,6 +5,7 @@
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OpenIddict.EntityFrameworkCore.Models;
@ -18,7 +19,11 @@ namespace OpenIddict.EntityFrameworkCore;
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkCoreAuthorizationConfiguration<TAuthorization, TApplication, TToken, TKey> : IEntityTypeConfiguration<TAuthorization>
public sealed class OpenIddictEntityFrameworkCoreAuthorizationConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IEntityTypeConfiguration<TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>

5
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreScopeConfiguration.cs

@ -5,6 +5,7 @@
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OpenIddict.EntityFrameworkCore.Models;
@ -16,7 +17,9 @@ namespace OpenIddict.EntityFrameworkCore;
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkCoreScopeConfiguration<TScope, TKey> : IEntityTypeConfiguration<TScope>
public sealed class OpenIddictEntityFrameworkCoreScopeConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IEntityTypeConfiguration<TScope>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TKey : notnull, IEquatable<TKey>
{

7
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreTokenConfiguration.cs

@ -5,6 +5,7 @@
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OpenIddict.EntityFrameworkCore.Models;
@ -18,7 +19,11 @@ namespace OpenIddict.EntityFrameworkCore;
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkCoreTokenConfiguration<TToken, TApplication, TAuthorization, TKey> : IEntityTypeConfiguration<TToken>
public sealed class OpenIddictEntityFrameworkCoreTokenConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IEntityTypeConfiguration<TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>

25
src/OpenIddict.EntityFrameworkCore/IOpenIddictEntityFrameworkCoreContext.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.ComponentModel;
namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Exposes the Entity Framework Core context used by the OpenIddict stores.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public interface IOpenIddictEntityFrameworkCoreContext
{
/// <summary>
/// Gets the <see cref="DbContext"/>.
/// </summary>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the
/// asynchronous operation, whose result returns the <see cref="DbContext"/>.
/// </returns>
ValueTask<DbContext> GetDbContextAsync(CancellationToken cancellationToken);
}

60
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs

@ -5,6 +5,8 @@
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenIddict.Core;
using OpenIddict.EntityFrameworkCore;
using OpenIddict.EntityFrameworkCore.Models;
@ -62,7 +64,8 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
/// Entity Framework Core entities, with the specified key type.
/// </summary>
/// <returns>The <see cref="OpenIddictEntityFrameworkCoreBuilder"/> instance.</returns>
public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities<TKey>()
public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>()
where TKey : notnull, IEquatable<TKey>
=> ReplaceDefaultEntities<OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
@ -74,20 +77,35 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
/// from the default OpenIddict Entity Framework Core entities.
/// </summary>
/// <returns>The <see cref="OpenIddictEntityFrameworkCoreBuilder"/> instance.</returns>
public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities<TApplication, TAuthorization, TScope, TToken, TKey>()
public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>()
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TKey : notnull, IEquatable<TKey>
{
Services.Configure<OpenIddictCoreOptions>(options =>
{
options.DefaultApplicationType = typeof(TApplication);
options.DefaultAuthorizationType = typeof(TAuthorization);
options.DefaultScopeType = typeof(TScope);
options.DefaultTokenType = typeof(TToken);
});
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictApplicationManager>(static provider =>
provider.GetRequiredService<OpenIddictApplicationManager<TApplication>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictAuthorizationManager>(static provider =>
provider.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeManager>(static provider =>
provider.GetRequiredService<OpenIddictScopeManager<TScope>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenManager>(static provider =>
provider.GetRequiredService<OpenIddictTokenManager<TToken>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictApplicationStore<TApplication>,
OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthorization, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictAuthorizationStore<TAuthorization>,
OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TApplication, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeStore<TScope>,
OpenIddictEntityFrameworkCoreScopeStore<TScope, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenStore<TToken>,
OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuthorization, TKey>>());
return this;
}
@ -97,28 +115,12 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
/// </summary>
/// <typeparam name="TContext">The type of the <see cref="DbContext"/> used by OpenIddict.</typeparam>
/// <returns>The <see cref="OpenIddictEntityFrameworkCoreBuilder"/> instance.</returns>
public OpenIddictEntityFrameworkCoreBuilder UseDbContext<TContext>()
where TContext : DbContext
=> UseDbContext(typeof(TContext));
/// <summary>
/// Configures the OpenIddict Entity Framework Core stores to use the specified database context type.
/// </summary>
/// <param name="type">The type of the <see cref="DbContext"/> used by OpenIddict.</param>
/// <returns>The <see cref="OpenIddictEntityFrameworkCoreBuilder"/> instance.</returns>
public OpenIddictEntityFrameworkCoreBuilder UseDbContext(Type type)
public OpenIddictEntityFrameworkCoreBuilder UseDbContext<TContext>() where TContext : DbContext
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(DbContext).IsAssignableFrom(type))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0232), nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped<
IOpenIddictEntityFrameworkCoreContext, OpenIddictEntityFrameworkCoreContext<TContext>>());
return Configure(options => options.DbContextType = type);
return this;
}
/// <inheritdoc/>

46
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreContext.cs

@ -0,0 +1,46 @@
/*
* 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.ComponentModel;
namespace OpenIddict.EntityFrameworkCore;
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public sealed class OpenIddictEntityFrameworkCoreContext<TContext> : IOpenIddictEntityFrameworkCoreContext
where TContext : DbContext
{
private readonly TContext? _context;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictEntityFrameworkCoreContext{TContext}"/> class.
/// </summary>
public OpenIddictEntityFrameworkCoreContext()
{
}
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictEntityFrameworkCoreContext{TContext}"/> class.
/// </summary>
/// <param name="context">The Entity Framework Core context, if available.</param>
public OpenIddictEntityFrameworkCoreContext(TContext? context) => _context = context;
/// <inheritdoc/>
public ValueTask<DbContext> GetDbContextAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return new(Task.FromCanceled<DbContext>(cancellationToken));
}
if (_context is not DbContext context)
{
return new(Task.FromException<DbContext>(new InvalidOperationException(SR.GetResourceString(SR.ID0471))));
}
return new(context);
}
}

8
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs

@ -4,6 +4,7 @@
* the license and the contributors participating to this project.
*/
using System.Diagnostics.CodeAnalysis;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore;
@ -12,7 +13,12 @@ namespace OpenIddict.EntityFrameworkCore;
/// Represents a model customizer able to register the entity sets
/// required by the OpenIddict stack in an Entity Framework Core context.
/// </summary>
public sealed class OpenIddictEntityFrameworkCoreCustomizer<TApplication, TAuthorization, TScope, TToken, TKey> : RelationalModelCustomizer
public sealed class OpenIddictEntityFrameworkCoreCustomizer<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : RelationalModelCustomizer
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>

22
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs

@ -39,20 +39,16 @@ public static class OpenIddictEntityFrameworkCoreExtensions
.SetDefaultScopeEntity<OpenIddictEntityFrameworkCoreScope>()
.SetDefaultTokenEntity<OpenIddictEntityFrameworkCoreToken>();
builder.ReplaceApplicationStoreResolver<OpenIddictEntityFrameworkCoreApplicationStoreResolver>()
.ReplaceAuthorizationStoreResolver<OpenIddictEntityFrameworkCoreAuthorizationStoreResolver>()
.ReplaceScopeStoreResolver<OpenIddictEntityFrameworkCoreScopeStoreResolver>()
.ReplaceTokenStoreResolver<OpenIddictEntityFrameworkCoreTokenStoreResolver>();
builder.ReplaceApplicationStore<OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreApplicationStore>()
.ReplaceAuthorizationStore<OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreAuthorizationStore>()
.ReplaceScopeStore<OpenIddictEntityFrameworkCoreScope, OpenIddictEntityFrameworkCoreScopeStore>()
.ReplaceTokenStore<OpenIddictEntityFrameworkCoreToken, OpenIddictEntityFrameworkCoreTokenStore>();
builder.Services.TryAddSingleton<OpenIddictEntityFrameworkCoreApplicationStoreResolver.TypeResolutionCache>();
builder.Services.TryAddSingleton<OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.TypeResolutionCache>();
builder.Services.TryAddSingleton<OpenIddictEntityFrameworkCoreScopeStoreResolver.TypeResolutionCache>();
builder.Services.TryAddSingleton<OpenIddictEntityFrameworkCoreTokenStoreResolver.TypeResolutionCache>();
builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreApplicationStore<,,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<,,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreScopeStore<,,>));
builder.Services.TryAddScoped(typeof(OpenIddictEntityFrameworkCoreTokenStore<,,,,>));
// Note: a default context factory is always registered to make debugging easier when
// no database context type was configured by the user: the default implementation
// registered here is automatically replaced by the UseDbContext<TContext>() API.
builder.Services.TryAddScoped<IOpenIddictEntityFrameworkCoreContext>(static provider =>
throw new InvalidOperationException(SR.GetResourceString(SR.ID0253)));
return new OpenIddictEntityFrameworkCoreBuilder(builder.Services);
}

44
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs

@ -4,8 +4,8 @@
* the license and the contributors participating to this project.
*/
using System;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.EntityFrameworkCore;
@ -54,7 +54,8 @@ public static class OpenIddictEntityFrameworkCoreHelpers
/// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbContextOptionsBuilder UseOpenIddict<TKey>(this DbContextOptionsBuilder builder)
public static DbContextOptionsBuilder UseOpenIddict<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this DbContextOptionsBuilder builder)
where TKey : notnull, IEquatable<TKey>
=> builder.UseOpenIddict<OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
@ -71,7 +72,9 @@ public static class OpenIddictEntityFrameworkCoreHelpers
/// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbContextOptionsBuilder<TContext> UseOpenIddict<TKey, TContext>(this DbContextOptionsBuilder<TContext> builder)
public static DbContextOptionsBuilder<TContext> UseOpenIddict<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey,
TContext>(this DbContextOptionsBuilder<TContext> builder)
where TKey : notnull, IEquatable<TKey>
where TContext : DbContext
{
@ -89,7 +92,12 @@ public static class OpenIddictEntityFrameworkCoreHelpers
/// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbContextOptionsBuilder UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey>(
public static DbContextOptionsBuilder UseOpenIddict<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(
this DbContextOptionsBuilder builder)
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
@ -102,8 +110,14 @@ public static class OpenIddictEntityFrameworkCoreHelpers
throw new ArgumentNullException(nameof(builder));
}
return builder.ReplaceService<IModelCustomizer, OpenIddictEntityFrameworkCoreCustomizer<
TApplication, TAuthorization, TScope, TToken, TKey>>();
return ReplaceService<IModelCustomizer,
OpenIddictEntityFrameworkCoreCustomizer<TApplication, TAuthorization, TScope, TToken, TKey>>(builder);
static DbContextOptionsBuilder ReplaceService<
TService,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(
DbContextOptionsBuilder builder) where TImplementation : TService
=> builder.ReplaceService<TService, TImplementation>();
}
/// <summary>
@ -116,7 +130,13 @@ public static class OpenIddictEntityFrameworkCoreHelpers
/// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static DbContextOptionsBuilder<TContext> UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey, TContext>(
public static DbContextOptionsBuilder<TContext> UseOpenIddict<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey,
TContext>(
this DbContextOptionsBuilder<TContext> builder)
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
@ -151,7 +171,8 @@ public static class OpenIddictEntityFrameworkCoreHelpers
/// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static ModelBuilder UseOpenIddict<TKey>(this ModelBuilder builder) where TKey : notnull, IEquatable<TKey>
public static ModelBuilder UseOpenIddict<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this ModelBuilder builder) where TKey : notnull, IEquatable<TKey>
=> builder.UseOpenIddict<OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreScope<TKey>,
@ -167,7 +188,12 @@ public static class OpenIddictEntityFrameworkCoreHelpers
/// </remarks>
/// <param name="builder">The builder used to configure the Entity Framework context.</param>
/// <returns>The Entity Framework context builder.</returns>
public static ModelBuilder UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey>(this ModelBuilder builder)
public static ModelBuilder UseOpenIddict<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this ModelBuilder builder)
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>

7
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs

@ -12,13 +12,6 @@ namespace OpenIddict.EntityFrameworkCore;
/// </summary>
public sealed class OpenIddictEntityFrameworkCoreOptions
{
/// <summary>
/// Gets or sets the concrete type of the <see cref="DbContext"/> used by the
/// OpenIddict Entity Framework Core stores. If this property is not populated,
/// an exception is thrown at runtime when trying to use the stores.
/// </summary>
public Type? DbContextType { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether bulk operations should be disabled.
/// </summary>

72
src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreApplicationStoreResolver.cs

@ -1,72 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFrameworkCore.Models;
using OpenIddict.Extensions;
namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Exposes a method allowing to resolve an application store.
/// </summary>
public sealed class OpenIddictEntityFrameworkCoreApplicationStoreResolver : IOpenIddictApplicationStoreResolver
{
private readonly TypeResolutionCache _cache;
private readonly IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictEntityFrameworkCoreApplicationStoreResolver(
TypeResolutionCache cache,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options,
IServiceProvider provider)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <summary>
/// Returns an application store compatible with the specified application type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <returns>An <see cref="IOpenIddictApplicationStore{TApplication}"/>.</returns>
public IOpenIddictApplicationStore<TApplication> Get<TApplication>() where TApplication : class
{
var store = _provider.GetService<IOpenIddictApplicationStore<TApplication>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TApplication), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreApplication<,,>)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0252));
var context = _options.CurrentValue.DbContextType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0253));
return typeof(OpenIddictEntityFrameworkCoreApplicationStore<,,,,>).MakeGenericType(
/* TApplication: */ key,
/* TAuthorization: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictApplicationStore<TApplication>) _provider.GetRequiredService(type);
}
// Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner
// service provider must be able to resolve scoped services (typically, the store they return).
// To avoid having to declare a static type resolution cache, a special cache service is used
// here and registered as a singleton dependency so that its content persists beyond the scope.
public sealed class TypeResolutionCache : ConcurrentDictionary<Type, Type> { }
}

72
src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.cs

@ -1,72 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFrameworkCore.Models;
using OpenIddict.Extensions;
namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Exposes a method allowing to resolve an authorization store.
/// </summary>
public sealed class OpenIddictEntityFrameworkCoreAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver
{
private readonly TypeResolutionCache _cache;
private readonly IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictEntityFrameworkCoreAuthorizationStoreResolver(
TypeResolutionCache cache,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options,
IServiceProvider provider)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <summary>
/// Returns an authorization store compatible with the specified authorization type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <returns>An <see cref="IOpenIddictAuthorizationStore{TAuthorization}"/>.</returns>
public IOpenIddictAuthorizationStore<TAuthorization> Get<TAuthorization>() where TAuthorization : class
{
var store = _provider.GetService<IOpenIddictAuthorizationStore<TAuthorization>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TAuthorization), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreAuthorization<,,>)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0254));
var context = _options.CurrentValue.DbContextType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0253));
return typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<,,,,>).MakeGenericType(
/* TAuthorization: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictAuthorizationStore<TAuthorization>) _provider.GetRequiredService(type);
}
// Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner
// service provider must be able to resolve scoped services (typically, the store they return).
// To avoid having to declare a static type resolution cache, a special cache service is used
// here and registered as a singleton dependency so that its content persists beyond the scope.
public sealed class TypeResolutionCache : ConcurrentDictionary<Type, Type> { }
}

70
src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreScopeStoreResolver.cs

@ -1,70 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFrameworkCore.Models;
using OpenIddict.Extensions;
namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Exposes a method allowing to resolve a scope store.
/// </summary>
public sealed class OpenIddictEntityFrameworkCoreScopeStoreResolver : IOpenIddictScopeStoreResolver
{
private readonly TypeResolutionCache _cache;
private readonly IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictEntityFrameworkCoreScopeStoreResolver(
TypeResolutionCache cache,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options,
IServiceProvider provider)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <summary>
/// Returns a scope store compatible with the specified scope type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <returns>An <see cref="IOpenIddictScopeStore{TScope}"/>.</returns>
public IOpenIddictScopeStore<TScope> Get<TScope>() where TScope : class
{
var store = _provider.GetService<IOpenIddictScopeStore<TScope>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TScope), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreScope<>)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0255));
var context = _options.CurrentValue.DbContextType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0253));
return typeof(OpenIddictEntityFrameworkCoreScopeStore<,,>).MakeGenericType(
/* TScope: */ key,
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictScopeStore<TScope>) _provider.GetRequiredService(type);
}
// Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner
// service provider must be able to resolve scoped services (typically, the store they return).
// To avoid having to declare a static type resolution cache, a special cache service is used
// here and registered as a singleton dependency so that its content persists beyond the scope.
public sealed class TypeResolutionCache : ConcurrentDictionary<Type, Type> { }
}

72
src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictEntityFrameworkCoreTokenStoreResolver.cs

@ -1,72 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.EntityFrameworkCore.Models;
using OpenIddict.Extensions;
namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Exposes a method allowing to resolve a token store.
/// </summary>
public sealed class OpenIddictEntityFrameworkCoreTokenStoreResolver : IOpenIddictTokenStoreResolver
{
private readonly TypeResolutionCache _cache;
private readonly IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictEntityFrameworkCoreTokenStoreResolver(
TypeResolutionCache cache,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options,
IServiceProvider provider)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_options = options ?? throw new ArgumentNullException(nameof(options));
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
/// <summary>
/// Returns a token store compatible with the specified token type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <returns>An <see cref="IOpenIddictTokenStore{TToken}"/>.</returns>
public IOpenIddictTokenStore<TToken> Get<TToken>() where TToken : class
{
var store = _provider.GetService<IOpenIddictTokenStore<TToken>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TToken), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictEntityFrameworkCoreToken<,,>)) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0256));
var context = _options.CurrentValue.DbContextType ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0253));
return typeof(OpenIddictEntityFrameworkCoreTokenStore<,,,,>).MakeGenericType(
/* TToken: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TAuthorization: */ root.GenericTypeArguments[2],
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictTokenStore<TToken>) _provider.GetRequiredService(type);
}
// Note: Entity Framework Core resolvers are registered as scoped dependencies as their inner
// service provider must be able to resolve scoped services (typically, the store they return).
// To avoid having to declare a static type resolution cache, a special cache service is used
// here and registered as a singleton dependency so that its content persists beyond the scope.
public sealed class TypeResolutionCache : ConcurrentDictionary<Type, Type> { }
}

182
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs

@ -24,16 +24,14 @@ namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictEntityFrameworkCoreApplicationStore<TContext> :
public class OpenIddictEntityFrameworkCoreApplicationStore :
OpenIddictEntityFrameworkCoreApplicationStore<OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreToken, TContext, string>
where TContext : DbContext
OpenIddictEntityFrameworkCoreToken, string>
{
public OpenIddictEntityFrameworkCoreApplicationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
@ -43,18 +41,17 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TContext> :
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreApplicationStore<TContext, TKey> :
public class OpenIddictEntityFrameworkCoreApplicationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> :
OpenIddictEntityFrameworkCoreApplicationStore<OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>, TContext, TKey>
where TContext : DbContext
OpenIddictEntityFrameworkCoreToken<TKey>, TKey>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreApplicationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
@ -67,18 +64,20 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TContext, TKey> :
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthorization, TToken, TContext, TKey> : IOpenIddictApplicationStore<TApplication>
public class OpenIddictEntityFrameworkCoreApplicationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TContext : DbContext
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreApplicationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
@ -94,31 +93,20 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected TContext Context { get; }
protected IOpenIddictEntityFrameworkCoreContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
=> await Applications.AsQueryable().LongCountAsync(cancellationToken);
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TApplication>().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TResult>(Func<IQueryable<TApplication>, IQueryable<TResult>> query, CancellationToken cancellationToken)
@ -128,7 +116,9 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
throw new ArgumentNullException(nameof(query));
}
return await query(Applications).LongCountAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TApplication>()).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
@ -139,9 +129,10 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
throw new ArgumentNullException(nameof(application));
}
Context.Add(application);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Add(application);
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
@ -152,43 +143,45 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
throw new ArgumentNullException(nameof(application));
}
var context = await Context.GetDbContextAsync(cancellationToken);
#if SUPPORTS_BULK_DBSET_OPERATIONS
if (!Options.CurrentValue.DisableBulkOperations)
{
var strategy = Context.Database.CreateExecutionStrategy();
var strategy = context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
using var transaction = await Context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
using var transaction = await context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
// Remove all the tokens associated with the application.
await (from token in Tokens
await (from token in context.Set<TToken>()
where token.Application!.Id!.Equals(application.Id)
select token).ExecuteDeleteAsync(cancellationToken);
// Remove all the authorizations associated with the application and
// the tokens attached to these implicit or explicit authorizations.
await (from authorization in Authorizations
await (from authorization in context.Set<TAuthorization>()
where authorization.Application!.Id!.Equals(application.Id)
select authorization).ExecuteDeleteAsync(cancellationToken);
// Note: calling DbContext.SaveChangesAsync() is not necessary
// with bulk delete operations as they are executed immediately.
Context.Remove(application);
context.Remove(application);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(application).State = EntityState.Unchanged;
context.Entry(application).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
}
@ -204,8 +197,8 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
Task<List<TAuthorization>> ListAuthorizationsAsync()
=> (from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking()
join element in Applications.AsTracking() on authorization.Application!.Id equals element.Id
=> (from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Tokens).AsTracking()
join element in context.Set<TApplication>().AsTracking() on authorization.Application!.Id equals element.Id
where element.Id!.Equals(application.Id)
select authorization).ToListAsync(cancellationToken);
@ -215,19 +208,19 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
Task<List<TToken>> ListTokensAsync()
=> (from token in Tokens.AsTracking()
=> (from token in context.Set<TToken>().AsTracking()
where token.Authorization == null
join element in Applications.AsTracking() on token.Application!.Id equals element.Id
join element in context.Set<TApplication>().AsTracking() on token.Application!.Id equals element.Id
where element.Id!.Equals(application.Id)
select token).ToListAsync(cancellationToken);
var strategy = Context.Database.CreateExecutionStrategy();
var strategy = context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
using var transaction = await Context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
using var transaction = await context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
// Remove all the authorizations associated with the application and
// the tokens attached to these implicit or explicit authorizations.
@ -236,40 +229,40 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
{
foreach (var token in authorization.Tokens)
{
Context.Remove(token);
context.Remove(token);
}
Context.Remove(authorization);
context.Remove(authorization);
}
// Remove all the tokens associated with the application.
var tokens = await ListTokensAsync();
foreach (var token in tokens)
{
Context.Remove(token);
context.Remove(token);
}
Context.Remove(application);
context.Remove(application);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(application).State = EntityState.Unchanged;
context.Entry(application).State = EntityState.Unchanged;
foreach (var authorization in authorizations)
{
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
}
foreach (var token in tokens)
{
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
}
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -279,45 +272,48 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
}
/// <inheritdoc/>
public virtual ValueTask<TApplication?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TApplication?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
return GetTrackedEntity() is TApplication application ? new(application) : new(QueryAsync());
var context = await Context.GetDbContextAsync(cancellationToken);
return GetTrackedEntity() is TApplication application ? application : await QueryAsync();
TApplication? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TApplication>()
(from entry in context.ChangeTracker.Entries<TApplication>()
where string.Equals(entry.Entity.ClientId, identifier, StringComparison.Ordinal)
select entry.Entity).FirstOrDefault();
Task<TApplication?> QueryAsync() =>
(from application in Applications.AsTracking()
(from application in context.Set<TApplication>().AsTracking()
where application.ClientId == identifier
select application).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<TApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return GetTrackedEntity() is TApplication application ? new(application) : new(QueryAsync());
return GetTrackedEntity() is TApplication application ? application : await QueryAsync();
TApplication? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TApplication>()
(from entry in context.ChangeTracker.Entries<TApplication>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TApplication?> QueryAsync() =>
(from application in Applications.AsTracking()
(from application in context.Set<TApplication>().AsTracking()
where application.Id!.Equals(key)
select application).FirstOrDefaultAsync(cancellationToken);
}
@ -341,7 +337,9 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
async IAsyncEnumerable<TApplication> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var applications = (from application in Applications.AsTracking()
var context = await Context.GetDbContextAsync(cancellationToken);
var applications = (from application in context.Set<TApplication>().AsTracking()
where application.PostLogoutRedirectUris!.Contains(uri)
select application).AsAsyncEnumerable(cancellationToken);
@ -375,7 +373,9 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
async IAsyncEnumerable<TApplication> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var applications = (from application in Applications.AsTracking()
var context = await Context.GetDbContextAsync(cancellationToken);
var applications = (from application in context.Set<TApplication>().AsTracking()
where application.RedirectUris!.Contains(uri)
select application).AsAsyncEnumerable(cancellationToken);
@ -411,7 +411,9 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
throw new ArgumentNullException(nameof(query));
}
return await query(Applications.AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TApplication>().AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
@ -804,9 +806,12 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TApplication> ListAsync(int? count, int? offset, CancellationToken cancellationToken)
public virtual async IAsyncEnumerable<TApplication> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var query = Applications.AsQueryable().OrderBy(application => application.Id!).AsTracking();
var context = await Context.GetDbContextAsync(cancellationToken);
var query = context.Set<TApplication>().OrderBy(application => application.Id!).AsTracking();
if (offset.HasValue)
{
@ -818,7 +823,10 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
query = query.Take(count.Value);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var application in query.AsAsyncEnumerable(cancellationToken))
{
yield return application;
}
}
/// <inheritdoc/>
@ -831,7 +839,17 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
throw new ArgumentNullException(nameof(query));
}
return query(Applications.AsTracking(), state).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var application in query(context.Set<TApplication>().AsTracking(), state).AsAsyncEnumerable(cancellationToken))
{
yield return application;
}
}
}
/// <inheritdoc/>
@ -960,7 +978,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
throw new ArgumentNullException(nameof(application));
}
application.JsonWebKeySet = set is not null ? JsonSerializer.Serialize(set) : null;
application.JsonWebKeySet = set is not null ? JsonSerializer.Serialize(set, OpenIddictSerializer.Default.JsonWebKeySet) : null;
return default;
}
@ -1201,23 +1219,25 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
throw new ArgumentNullException(nameof(application));
}
Context.Attach(application);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Attach(application);
// Generate a new concurrency token and attach it
// to the application before persisting the changes.
application.ConcurrencyToken = Guid.NewGuid().ToString();
Context.Update(application);
context.Update(application);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(application).State = EntityState.Unchanged;
context.Entry(application).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
}
@ -1235,7 +1255,11 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
return default;
}
return (TKey?) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
return (TKey?) GetConverter().ConvertFromInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
/// <summary>
@ -1250,6 +1274,10 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthor
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
return GetConverter().ConvertToInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
}

285
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs

@ -7,6 +7,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
@ -22,16 +23,14 @@ namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictEntityFrameworkCoreAuthorizationStore<TContext> :
public class OpenIddictEntityFrameworkCoreAuthorizationStore :
OpenIddictEntityFrameworkCoreAuthorizationStore<OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreToken, TContext, string>
where TContext : DbContext
OpenIddictEntityFrameworkCoreToken, string>
{
public OpenIddictEntityFrameworkCoreAuthorizationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
@ -41,18 +40,17 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TContext> :
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreAuthorizationStore<TContext, TKey> :
public class OpenIddictEntityFrameworkCoreAuthorizationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> :
OpenIddictEntityFrameworkCoreAuthorizationStore<OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>, TContext, TKey>
where TContext : DbContext
OpenIddictEntityFrameworkCoreToken<TKey>, TKey>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreAuthorizationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
@ -65,18 +63,20 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TContext, TKey> :
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TApplication, TToken, TContext, TKey> : IOpenIddictAuthorizationStore<TAuthorization>
public class OpenIddictEntityFrameworkCoreAuthorizationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TContext : DbContext
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreAuthorizationStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
@ -92,31 +92,20 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected TContext Context { get; }
protected IOpenIddictEntityFrameworkCoreContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
=> await Authorizations.AsQueryable().LongCountAsync(cancellationToken);
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TAuthorization>().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TResult>(Func<IQueryable<TAuthorization>, IQueryable<TResult>> query, CancellationToken cancellationToken)
@ -126,7 +115,9 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentNullException(nameof(query));
}
return await query(Authorizations).LongCountAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TAuthorization>()).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
@ -137,9 +128,11 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentNullException(nameof(authorization));
}
Context.Add(authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Add(authorization);
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
@ -150,37 +143,39 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentNullException(nameof(authorization));
}
var context = await Context.GetDbContextAsync(cancellationToken);
#if SUPPORTS_BULK_DBSET_OPERATIONS
if (!Options.CurrentValue.DisableBulkOperations)
{
var strategy = Context.Database.CreateExecutionStrategy();
var strategy = context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
using var transaction = await Context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
using var transaction = await context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
// Remove all the tokens associated with the authorization.
await (from token in Tokens.AsTracking()
await (from token in context.Set<TToken>().AsTracking()
where token.Authorization!.Id!.Equals(authorization.Id)
select token).ExecuteDeleteAsync(cancellationToken);
// Note: calling DbContext.SaveChangesAsync() is not necessary
// with bulk delete operations as they are executed immediately.
Context.Remove(authorization);
context.Remove(authorization);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0241), exception);
}
@ -196,42 +191,42 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
Task<List<TToken>> ListTokensAsync()
=> (from token in Tokens.AsTracking()
join element in Authorizations.AsTracking() on token.Authorization!.Id equals element.Id
=> (from token in context.Set<TToken>().AsTracking()
join element in context.Set<TAuthorization>().AsTracking() on token.Authorization!.Id equals element.Id
where element.Id!.Equals(authorization.Id)
select token).ToListAsync(cancellationToken);
var strategy = Context.Database.CreateExecutionStrategy();
var strategy = context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
using var transaction = await Context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
using var transaction = await context.CreateTransactionAsync(IsolationLevel.Serializable, cancellationToken);
// Remove all the tokens associated with the authorization.
var tokens = await ListTokensAsync();
foreach (var token in tokens)
{
Context.Remove(token);
context.Remove(token);
}
Context.Remove(authorization);
context.Remove(authorization);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
foreach (var token in tokens)
{
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
}
throw new ConcurrencyException(SR.GetResourceString(SR.ID0241), exception);
@ -246,7 +241,9 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
string? status, string? type,
ImmutableArray<string>? scopes, [EnumeratorCancellation] CancellationToken cancellationToken)
{
IQueryable<TAuthorization> query = Authorizations.Include(authorization => authorization.Application).AsTracking();
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TAuthorization> query = context.Set<TAuthorization>().Include(authorization => authorization.Application).AsTracking();
if (!string.IsNullOrEmpty(subject))
{
@ -263,7 +260,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
var key = ConvertIdentifierFromString(client);
query = from authorization in query
join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id
join application in context.Set<TApplication>().AsTracking() on authorization.Application!.Id equals application.Id
where application.Id!.Equals(key)
select authorization;
}
@ -290,63 +287,83 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TAuthorization> FindByApplicationIdAsync(
string identifier, CancellationToken cancellationToken)
public virtual IAsyncEnumerable<TAuthorization> FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations
// can't be filtered using authorization.Application.Id.Equals(key). To work around
// this issue, this query uses use an explicit join to apply the equality check.
//
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
return ExecuteAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
async IAsyncEnumerable<TAuthorization> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id
where application.Id!.Equals(key)
select authorization).AsAsyncEnumerable(cancellationToken);
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations
// can't be filtered using authorization.Application.Id.Equals(key). To work around
// this issue, this query uses use an explicit join to apply the equality check.
//
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
await foreach (var authorization in
(from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Application).AsTracking()
join application in context.Set<TApplication>().AsTracking() on authorization.Application!.Id equals application.Id
where application.Id!.Equals(key)
select authorization).AsAsyncEnumerable(cancellationToken))
{
yield return authorization;
}
}
}
/// <inheritdoc/>
public virtual ValueTask<TAuthorization?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TAuthorization?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return GetTrackedEntity() is TAuthorization authorization ? new(authorization) : new(QueryAsync());
return GetTrackedEntity() is TAuthorization authorization ? authorization : await QueryAsync();
TAuthorization? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TAuthorization>()
(from entry in context.ChangeTracker.Entries<TAuthorization>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TAuthorization?> QueryAsync() =>
(from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
(from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Application).AsTracking()
where authorization.Id!.Equals(key)
select authorization).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TAuthorization> FindBySubjectAsync(
string subject, CancellationToken cancellationToken)
public virtual IAsyncEnumerable<TAuthorization> FindBySubjectAsync(string subject, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject));
}
return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
where authorization.Subject == subject
select authorization).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TAuthorization> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var authorization in
(from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Application).AsTracking()
where authorization.Subject == subject
select authorization).AsAsyncEnumerable(cancellationToken))
{
yield return authorization;
}
}
}
/// <inheritdoc/>
@ -357,10 +374,12 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentNullException(nameof(authorization));
}
var context = await Context.GetDbContextAsync(cancellationToken);
// If the application is not attached to the authorization, try to load it manually.
if (authorization.Application is null)
{
var reference = Context.Entry(authorization).Reference(entry => entry.Application);
var reference = context.Entry(authorization).Reference(entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
@ -387,8 +406,10 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentNullException(nameof(query));
}
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(
Authorizations.Include(authorization => authorization.Application)
context.Set<TAuthorization>().Include(authorization => authorization.Application)
.AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
}
@ -544,11 +565,15 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TAuthorization> ListAsync(int? count, int? offset, CancellationToken cancellationToken)
public virtual async IAsyncEnumerable<TAuthorization> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var query = Authorizations.Include(authorization => authorization.Application)
.OrderBy(authorization => authorization.Id!)
.AsTracking();
var context = await Context.GetDbContextAsync(cancellationToken);
var query = context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
.OrderBy(authorization => authorization.Id!)
.AsTracking();
if (offset.HasValue)
{
@ -560,7 +585,10 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
query = query.Take(count.Value);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var authorization in query.AsAsyncEnumerable(cancellationToken))
{
yield return authorization;
}
}
/// <inheritdoc/>
@ -573,14 +601,27 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentNullException(nameof(query));
}
return query(
Authorizations.Include(authorization => authorization.Application)
.AsTracking(), state).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var authorization in query(
context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking(), state).AsAsyncEnumerable(cancellationToken))
{
yield return authorization;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
List<Exception>? exceptions = null;
var result = 0L;
@ -603,7 +644,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
try
{
var count = await
(from authorization in Authorizations
(from authorization in context.Set<TAuthorization>()
where authorization.CreationDate < date
where authorization.Status != Statuses.Valid || authorization.Type == AuthorizationTypes.AdHoc
where !authorization.Tokens.Any()
@ -631,17 +672,17 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
else
#endif
{
var strategy = Context.Database.CreateExecutionStrategy();
var strategy = context.Database.CreateExecutionStrategy();
var count = await strategy.ExecuteAsync(async () =>
{
// To prevent concurrency exceptions from being thrown if an entry is modified
// after it was retrieved from the database, the following logic is executed in
// a repeatable read transaction, that will put a lock on the retrieved entries
// and thus prevent them from being concurrently modified outside this block.
using var transaction = await Context.CreateTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken);
using var transaction = await context.CreateTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken);
var authorizations = await
(from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking()
(from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Tokens).AsTracking()
where authorization.CreationDate < date
where authorization.Status != Statuses.Valid || authorization.Type == AuthorizationTypes.AdHoc
where !authorization.Tokens.Any()
@ -654,11 +695,11 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
// from the database since the transaction level is deliberately limited to
// repeatable read instead of serializable for performance reasons). In this
// case, the operation will fail, which is considered an acceptable risk.
Context.RemoveRange(authorizations);
context.RemoveRange(authorizations);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
@ -692,9 +733,11 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
/// <inheritdoc/>
public virtual async ValueTask<long> RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TAuthorization> query = Options.CurrentValue.DisableBulkOperations ?
Authorizations.Include(authorization => authorization.Application).AsTracking() :
Authorizations;
context.Set<TAuthorization>().Include(authorization => authorization.Application).AsTracking() :
context.Set<TAuthorization>();
if (!string.IsNullOrEmpty(subject))
{
@ -711,7 +754,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
var key = ConvertIdentifierFromString(client);
query = from authorization in query
join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id
join application in context.Set<TApplication>().AsTracking() on authorization.Application!.Id equals application.Id
where application.Id!.Equals(key)
select authorization;
}
@ -752,13 +795,13 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -785,13 +828,14 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
#if SUPPORTS_BULK_DBSET_OPERATIONS
if (!Options.CurrentValue.DisableBulkOperations)
{
return await (
from authorization in Authorizations
from authorization in context.Set<TAuthorization>()
where authorization.Application!.Id!.Equals(key)
select authorization).ExecuteUpdateAsync(entity => entity.SetProperty(
authorization => authorization.Status, Statuses.Revoked), cancellationToken);
@ -810,8 +854,8 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
//
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
foreach (var authorization in await (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id
foreach (var authorization in await (from authorization in context.Set<TAuthorization>().Include(authorization => authorization.Application).AsTracking()
join application in context.Set<TApplication>().AsTracking() on authorization.Application!.Id equals application.Id
where application.Id!.Equals(key)
select authorization).ToListAsync(cancellationToken))
{
@ -819,13 +863,13 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -852,11 +896,13 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
}
var context = await Context.GetDbContextAsync(cancellationToken);
#if SUPPORTS_BULK_DBSET_OPERATIONS
if (!Options.CurrentValue.DisableBulkOperations)
{
return await (
from authorization in Authorizations
from authorization in context.Set<TAuthorization>()
where authorization.Subject == subject
select authorization).ExecuteUpdateAsync(entity => entity.SetProperty(
authorization => authorization.Status, Statuses.Revoked), cancellationToken);
@ -869,7 +915,9 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
var result = 0L;
foreach (var authorization in await (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
foreach (var authorization in await (from authorization in context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Subject == subject
select authorization).ToListAsync(cancellationToken))
{
@ -877,13 +925,13 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -911,10 +959,13 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentNullException(nameof(authorization));
}
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier))
{
#if SUPPORTS_DBSET_VALUETASK_FINDASYNC
authorization.Application = await Applications.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken);
authorization.Application = await context.Set<TApplication>()
.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken);
#else
// Warning: when targeting older TFMs, FindAsync() is deliberately not used to work around a breaking
// change introduced in Entity Framework Core 3.x (where a ValueTask instead of a Task is now returned).
@ -925,12 +976,12 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new InvalidOperationException(SR.GetResourceString(SR.ID0244));
TApplication? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TApplication>()
(from entry in context.ChangeTracker.Entries<TApplication>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TApplication?> QueryAsync() =>
(from application in Applications.AsTracking()
(from application in context.Set<TApplication>().AsTracking()
where application.Id!.Equals(key)
select application).FirstOrDefaultAsync(cancellationToken);
#endif
@ -941,7 +992,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
// If the application is not attached to the authorization, try to load it manually.
if (authorization.Application is null)
{
var reference = Context.Entry(authorization).Reference(entry => entry.Application);
var reference = context.Entry(authorization).Reference(entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
@ -1095,23 +1146,25 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
throw new ArgumentNullException(nameof(authorization));
}
Context.Attach(authorization);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Attach(authorization);
// Generate a new concurrency token and attach it
// to the authorization before persisting the changes.
authorization.ConcurrencyToken = Guid.NewGuid().ToString();
Context.Update(authorization);
context.Update(authorization);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(authorization).State = EntityState.Unchanged;
context.Entry(authorization).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0241), exception);
}
@ -1129,7 +1182,11 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
return default;
}
return (TKey?) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
return (TKey?) GetConverter().ConvertFromInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
/// <summary>
@ -1144,6 +1201,10 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TAp
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
return GetConverter().ConvertToInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
}

149
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs

@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
@ -21,13 +22,11 @@ namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictEntityFrameworkCoreScopeStore<TContext> : OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope, TContext, string>
where TContext : DbContext
public class OpenIddictEntityFrameworkCoreScopeStore : OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope, string>
{
public OpenIddictEntityFrameworkCoreScopeStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
@ -37,15 +36,14 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TContext> : OpenIddictEntit
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreScopeStore<TContext, TKey> : OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope<TKey>, TContext, TKey>
where TContext : DbContext
public class OpenIddictEntityFrameworkCoreScopeStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : OpenIddictEntityFrameworkCoreScopeStore<OpenIddictEntityFrameworkCoreScope<TKey>, TKey>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreScopeStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
@ -56,16 +54,16 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TContext, TKey> : OpenIddic
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : IOpenIddictScopeStore<TScope>
public class OpenIddictEntityFrameworkCoreScopeStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictScopeStore<TScope>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TContext : DbContext
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreScopeStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
@ -81,21 +79,20 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected TContext Context { get; }
protected IOpenIddictEntityFrameworkCoreContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TScope"/> entity.
/// </summary>
private DbSet<TScope> Scopes => Context.Set<TScope>();
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
=> await Scopes.AsQueryable().LongCountAsync(cancellationToken);
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TScope>().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TResult>(Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken)
@ -105,7 +102,9 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
throw new ArgumentNullException(nameof(query));
}
return await query(Scopes).LongCountAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TScope>()).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
@ -116,9 +115,11 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
throw new ArgumentNullException(nameof(scope));
}
Scopes.Add(scope);
var context = await Context.GetDbContextAsync(cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
context.Add(scope);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
@ -129,80 +130,94 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
throw new ArgumentNullException(nameof(scope));
}
Context.Remove(scope);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Remove(scope);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(scope).State = EntityState.Unchanged;
context.Entry(scope).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0245), exception);
}
}
/// <inheritdoc/>
public virtual ValueTask<TScope?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TScope?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return GetTrackedEntity() is TScope scope ? new(scope) : new(QueryAsync());
return GetTrackedEntity() is TScope scope ? scope : await QueryAsync();
TScope? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TScope>()
(from entry in context.ChangeTracker.Entries<TScope>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TScope?> QueryAsync() =>
(from scope in Scopes.AsTracking()
(from scope in context.Set<TScope>().AsTracking()
where scope.Id!.Equals(key)
select scope).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<TScope?> FindByNameAsync(string name, CancellationToken cancellationToken)
public virtual async ValueTask<TScope?> FindByNameAsync(string name, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0202), nameof(name));
}
return GetTrackedEntity() is TScope scope ? new(scope) : new(QueryAsync());
var context = await Context.GetDbContextAsync(cancellationToken);
return GetTrackedEntity() is TScope scope ? scope : await QueryAsync();
TScope? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TScope>()
(from entry in context.ChangeTracker.Entries<TScope>()
where string.Equals(entry.Entity.Name, name, StringComparison.Ordinal)
select entry.Entity).FirstOrDefault();
Task<TScope?> QueryAsync() =>
(from scope in Scopes.AsTracking()
(from scope in context.Set<TScope>().AsTracking()
where scope.Name == name
select scope).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TScope> FindByNamesAsync(
ImmutableArray<string> names, CancellationToken cancellationToken)
public virtual IAsyncEnumerable<TScope> FindByNamesAsync(ImmutableArray<string> names, CancellationToken cancellationToken)
{
if (names.Any(string.IsNullOrEmpty))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0203), nameof(names));
}
// Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure
// ImmutableArray.Contains() (which is not fully supported by Entity Framework Core) is not used instead.
return (from scope in Scopes.AsTracking()
where Enumerable.Contains(names, scope.Name)
select scope).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TScope> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
// Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure
// ImmutableArray.Contains() (which is not fully supported by Entity Framework Core) is not used instead.
await foreach (var scope in (from scope in context.Set<TScope>().AsTracking()
where Enumerable.Contains(names, scope.Name)
select scope).AsAsyncEnumerable(cancellationToken))
{
yield return scope;
}
}
}
/// <inheritdoc/>
@ -224,7 +239,9 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
async IAsyncEnumerable<TScope> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var scopes = (from scope in Scopes.AsTracking()
var context = await Context.GetDbContextAsync(cancellationToken);
var scopes = (from scope in context.Set<TScope>().AsTracking()
where scope.Resources!.Contains(resource)
select scope).AsAsyncEnumerable(cancellationToken);
@ -249,7 +266,9 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
throw new ArgumentNullException(nameof(query));
}
return await query(Scopes.AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TScope>().AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
@ -470,9 +489,12 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TScope> ListAsync(int? count, int? offset, CancellationToken cancellationToken)
public virtual async IAsyncEnumerable<TScope> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var query = Scopes.AsQueryable().OrderBy(scope => scope.Id!).AsTracking();
var context = await Context.GetDbContextAsync(cancellationToken);
var query = context.Set<TScope>().OrderBy(scope => scope.Id!).AsTracking();
if (offset.HasValue)
{
@ -484,7 +506,10 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
query = query.Take(count.Value);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var scope in query.AsAsyncEnumerable(cancellationToken))
{
yield return scope;
}
}
/// <inheritdoc/>
@ -497,7 +522,17 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
throw new ArgumentNullException(nameof(query));
}
return query(Scopes.AsTracking(), state).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var scope in query(context.Set<TScope>().AsTracking(), state).AsAsyncEnumerable(cancellationToken))
{
yield return scope;
}
}
}
/// <inheritdoc/>
@ -701,23 +736,25 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
throw new ArgumentNullException(nameof(scope));
}
Context.Attach(scope);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Attach(scope);
// Generate a new concurrency token and attach it
// to the scope before persisting the changes.
scope.ConcurrencyToken = Guid.NewGuid().ToString();
Context.Update(scope);
context.Update(scope);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(scope).State = EntityState.Unchanged;
context.Entry(scope).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0245), exception);
}
@ -735,7 +772,11 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
return default;
}
return (TKey?) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
return (TKey?) GetConverter().ConvertFromInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
/// <summary>
@ -750,6 +791,10 @@ public class OpenIddictEntityFrameworkCoreScopeStore<TScope, TContext, TKey> : I
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
return GetConverter().ConvertToInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
}

358
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs

@ -7,6 +7,8 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
@ -21,16 +23,14 @@ namespace OpenIddict.EntityFrameworkCore;
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictEntityFrameworkCoreTokenStore<TContext> :
public class OpenIddictEntityFrameworkCoreTokenStore :
OpenIddictEntityFrameworkCoreTokenStore<OpenIddictEntityFrameworkCoreToken,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreAuthorization, TContext, string>
where TContext : DbContext
OpenIddictEntityFrameworkCoreAuthorization, string>
{
public OpenIddictEntityFrameworkCoreTokenStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
@ -40,18 +40,17 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TContext> :
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreTokenStore<TContext, TKey> :
public class OpenIddictEntityFrameworkCoreTokenStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> :
OpenIddictEntityFrameworkCoreTokenStore<OpenIddictEntityFrameworkCoreToken<TKey>,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>, TContext, TKey>
where TContext : DbContext
OpenIddictEntityFrameworkCoreAuthorization<TKey>, TKey>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreTokenStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
@ -64,18 +63,20 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TContext, TKey> :
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuthorization, TContext, TKey> : IOpenIddictTokenStore<TToken>
public class OpenIddictEntityFrameworkCoreTokenStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TContext : DbContext
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreTokenStore(
IMemoryCache cache,
TContext context,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{
Cache = cache ?? throw new ArgumentNullException(nameof(cache));
@ -91,31 +92,20 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected TContext Context { get; }
protected IOpenIddictEntityFrameworkCoreContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
=> await Tokens.AsQueryable().LongCountAsync(cancellationToken);
{
var context = await Context.GetDbContextAsync(cancellationToken);
return await context.Set<TToken>().AsQueryable().LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<long> CountAsync<TResult>(Func<IQueryable<TToken>, IQueryable<TResult>> query, CancellationToken cancellationToken)
@ -125,7 +115,9 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(query));
}
return await query(Tokens).LongCountAsync(cancellationToken);
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TToken>()).LongCountAsync(cancellationToken);
}
/// <inheritdoc/>
@ -136,9 +128,11 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(token));
}
Context.Add(token);
var context = await Context.GetDbContextAsync(cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
context.Add(token);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
@ -149,28 +143,35 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(token));
}
Context.Remove(token);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Remove(token);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0247), exception);
}
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TToken> FindAsync(
public virtual async IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client,
string? status, string? type, CancellationToken cancellationToken)
string? status, string? type, [EnumeratorCancellation] CancellationToken cancellationToken)
{
IQueryable<TToken> query = Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking();
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TToken> query = context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking();
if (!string.IsNullOrEmpty(subject))
{
@ -187,7 +188,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
var key = ConvertIdentifierFromString(client);
query = from authorization in query
join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id
join application in context.Set<TApplication>().AsTracking() on authorization.Application!.Id equals application.Id
where application.Id!.Equals(key)
select authorization;
}
@ -202,7 +203,10 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
query = query.Where(token => token.Type == type);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var token in query.AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
/// <inheritdoc/>
@ -213,18 +217,31 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
// Note: due to a bug in Entity Framework Core's query visitor, the tokens
// can't be filtered using token.Application.Id.Equals(key). To work around
// this issue, this query uses use an explicit join to apply the equality check.
//
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
return ExecuteAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
async IAsyncEnumerable<TToken> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
// Note: due to a bug in Entity Framework Core's query visitor, the tokens
// can't be filtered using token.Application.Id.Equals(key). To work around
// this issue, this query uses use an explicit join to apply the equality check.
//
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
join application in Applications.AsTracking() on token.Application!.Id equals application.Id
where application.Id!.Equals(key)
select token).AsAsyncEnumerable(cancellationToken);
await foreach (var token in
(from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join application in context.Set<TApplication>().AsTracking() on token.Application!.Id equals application.Id
where application.Id!.Equals(key)
select token).AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
}
/// <inheritdoc/>
@ -235,60 +252,76 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
// Note: due to a bug in Entity Framework Core's query visitor, the tokens
// can't be filtered using token.Authorization.Id.Equals(key). To work around
// this issue, this query uses use an explicit join to apply the equality check.
//
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
return ExecuteAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
async IAsyncEnumerable<TToken> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
// Note: due to a bug in Entity Framework Core's query visitor, the tokens
// can't be filtered using token.Authorization.Id.Equals(key). To work around
// this issue, this query uses use an explicit join to apply the equality check.
//
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
join authorization in Authorizations.AsTracking() on token.Authorization!.Id equals authorization.Id
where authorization.Id!.Equals(key)
select token).AsAsyncEnumerable(cancellationToken);
await foreach (var token in
(from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join authorization in context.Set<TAuthorization>().AsTracking() on token.Authorization!.Id equals authorization.Id
where authorization.Id!.Equals(key)
select token).AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
}
/// <inheritdoc/>
public virtual ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TToken?> FindByIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return GetTrackedEntity() is TToken token ? new(token) : new(QueryAsync());
return GetTrackedEntity() is TToken token ? token : await QueryAsync();
TToken? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TToken>()
(from entry in context.ChangeTracker.Entries<TToken>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TToken?> QueryAsync() =>
(from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
(from token in context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.Id!.Equals(key)
select token).FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc/>
public virtual ValueTask<TToken?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken)
public virtual async ValueTask<TToken?> FindByReferenceIdAsync(string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
return GetTrackedEntity() is TToken token ? new(token) : new(QueryAsync());
var context = await Context.GetDbContextAsync(cancellationToken);
return GetTrackedEntity() is TToken token ? token : await QueryAsync();
TToken? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TToken>()
(from entry in context.ChangeTracker.Entries<TToken>()
where string.Equals(entry.Entity.ReferenceId, identifier, StringComparison.Ordinal)
select entry.Entity).FirstOrDefault();
Task<TToken?> QueryAsync() =>
(from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
(from token in context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.ReferenceId == identifier
select token).FirstOrDefaultAsync(cancellationToken);
}
@ -301,9 +334,20 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject));
}
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.Subject == subject
select token).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TToken> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var token in
(from token in context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.Subject == subject
select token).AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
}
/// <inheritdoc/>
@ -314,10 +358,12 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(token));
}
var context = await Context.GetDbContextAsync(cancellationToken);
// If the application is not attached to the token, try to load it manually.
if (token.Application is null)
{
var reference = Context.Entry(token).Reference(entry => entry.Application);
var reference = context.Entry(token).Reference(entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
@ -344,7 +390,9 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(query));
}
return await query(Tokens.Include(token => token.Application)
var context = await Context.GetDbContextAsync(cancellationToken);
return await query(context.Set<TToken>().Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
}
@ -357,10 +405,12 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(token));
}
var context = await Context.GetDbContextAsync(cancellationToken);
// If the authorization is not attached to the token, try to load it manually.
if (token.Authorization is null)
{
var reference = Context.Entry(token).Reference(entry => entry.Authorization);
var reference = context.Entry(token).Reference(entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached)
{
return null;
@ -542,12 +592,17 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
}
/// <inheritdoc/>
public virtual IAsyncEnumerable<TToken> ListAsync(int? count, int? offset, CancellationToken cancellationToken)
public virtual async IAsyncEnumerable<TToken> ListAsync(int? count, int? offset,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var query = Tokens.Include(token => token.Application)
.Include(token => token.Authorization)
.OrderBy(token => token.Id!)
.AsTracking();
var context = await Context.GetDbContextAsync(cancellationToken);
var query = context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.OrderBy(token => token.Id!)
.AsTracking();
if (offset.HasValue)
{
@ -559,7 +614,10 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
query = query.Take(count.Value);
}
return query.AsAsyncEnumerable(cancellationToken);
await foreach (var token in query.AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
/// <inheritdoc/>
@ -572,15 +630,28 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(query));
}
return query(
Tokens.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking(), state).AsAsyncEnumerable(cancellationToken);
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<TResult> ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
await foreach (var token in query(
context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking(), state).AsAsyncEnumerable(cancellationToken))
{
yield return token;
}
}
}
/// <inheritdoc/>
public virtual async ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
List<Exception>? exceptions = null;
var result = 0L;
@ -603,7 +674,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
try
{
var count = await
(from token in Tokens
(from token in context.Set<TToken>()
where token.CreationDate < date
where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) ||
(token.Authorization != null && token.Authorization.Status != Statuses.Valid) ||
@ -632,17 +703,17 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
else
#endif
{
var strategy = Context.Database.CreateExecutionStrategy();
var strategy = context.Database.CreateExecutionStrategy();
var count = await strategy.ExecuteAsync(async () =>
{
// To prevent concurrency exceptions from being thrown if an entry is modified
// after it was retrieved from the database, the following logic is executed in
// a repeatable read transaction, that will put a lock on the retrieved entries
// and thus prevent them from being concurrently modified outside this block.
using var transaction = await Context.CreateTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken);
using var transaction = await context.CreateTransactionAsync(IsolationLevel.RepeatableRead, cancellationToken);
var tokens = await
(from token in Tokens.AsTracking()
(from token in context.Set<TToken>().AsTracking()
where token.CreationDate < date
where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) ||
(token.Authorization != null && token.Authorization.Status != Statuses.Valid) ||
@ -652,11 +723,11 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
if (tokens.Count is not 0)
{
Context.RemoveRange(tokens);
context.RemoveRange(tokens);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
@ -690,9 +761,11 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
/// <inheritdoc/>
public virtual async ValueTask<long> RevokeAsync(string? subject, string? client, string? status, string ?type, CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
IQueryable<TToken> query = Options.CurrentValue.DisableBulkOperations ?
Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() :
Tokens;
context.Set<TToken>().Include(token => token.Application).Include(token => token.Authorization).AsTracking() :
context.Set<TToken>();
if (!string.IsNullOrEmpty(subject))
{
@ -709,7 +782,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
var key = ConvertIdentifierFromString(client);
query = from authorization in query
join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id
join application in context.Set<TApplication>().AsTracking() on authorization.Application!.Id equals application.Id
where application.Id!.Equals(key)
select authorization;
}
@ -744,13 +817,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -777,13 +850,14 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
#if SUPPORTS_BULK_DBSET_OPERATIONS
if (!Options.CurrentValue.DisableBulkOperations)
{
return await (
from token in Tokens
from token in context.Set<TToken>()
where token.Application!.Id!.Equals(key)
select token).ExecuteUpdateAsync(entity => entity.SetProperty(
token => token.Status, Statuses.Revoked), cancellationToken);
@ -802,10 +876,11 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
//
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
foreach (var token in await (from token in Tokens.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join application in Applications.AsTracking() on token.Application!.Id equals application.Id
foreach (var token in await (from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join application in context.Set<TApplication>().AsTracking() on token.Application!.Id equals application.Id
where application.Id!.Equals(key)
select token).ToListAsync(cancellationToken))
{
@ -813,13 +888,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -846,13 +921,14 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var context = await Context.GetDbContextAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
#if SUPPORTS_BULK_DBSET_OPERATIONS
if (!Options.CurrentValue.DisableBulkOperations)
{
return await (
from token in Tokens
from token in context.Set<TToken>()
where token.Authorization!.Id!.Equals(key)
select token).ExecuteUpdateAsync(entity => entity.SetProperty(
token => token.Status, Statuses.Revoked), cancellationToken);
@ -871,10 +947,11 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
//
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
foreach (var token in await (from token in Tokens.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join authorization in Authorizations.AsTracking() on token.Authorization!.Id equals authorization.Id
foreach (var token in await (from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join authorization in context.Set<TAuthorization>().AsTracking() on token.Authorization!.Id equals authorization.Id
where authorization.Id!.Equals(key)
select token).ToListAsync(cancellationToken))
{
@ -882,13 +959,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -915,11 +992,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
}
var context = await Context.GetDbContextAsync(cancellationToken);
#if SUPPORTS_BULK_DBSET_OPERATIONS
if (!Options.CurrentValue.DisableBulkOperations)
{
return await (
from token in Tokens
from token in context.Set<TToken>()
where token.Subject == subject
select token).ExecuteUpdateAsync(entity => entity.SetProperty(
token => token.Status, Statuses.Revoked), cancellationToken);
@ -932,9 +1011,10 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
var result = 0L;
foreach (var token in await (from token in Tokens.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
foreach (var token in await (from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
where token.Subject == subject
select token).ToListAsync(cancellationToken))
{
@ -942,13 +1022,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= [];
exceptions.Add(exception);
@ -975,10 +1055,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(token));
}
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier))
{
#if SUPPORTS_DBSET_VALUETASK_FINDASYNC
token.Application = await Applications.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken);
token.Application = await context.Set<TApplication>()
.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken);
#else
// Warning: when targeting older TFMs, FindAsync() is deliberately not used to work around a breaking
// change introduced in Entity Framework Core 3.x (where a ValueTask instead of a Task is now returned).
@ -989,12 +1072,12 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new InvalidOperationException(SR.GetResourceString(SR.ID0250));
TApplication? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TApplication>()
(from entry in context.ChangeTracker.Entries<TApplication>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TApplication?> QueryAsync() =>
(from application in Applications.AsTracking()
(from application in context.Set<TApplication>().AsTracking()
where application.Id!.Equals(key)
select application).FirstOrDefaultAsync(cancellationToken);
#endif
@ -1005,7 +1088,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
// If the application is not attached to the token, try to load it manually.
if (token.Application is null)
{
var reference = Context.Entry(token).Reference(entry => entry.Application);
var reference = context.Entry(token).Reference(entry => entry.Application);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
@ -1026,10 +1109,13 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(token));
}
var context = await Context.GetDbContextAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier))
{
#if SUPPORTS_DBSET_VALUETASK_FINDASYNC
token.Authorization = await Authorizations.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken);
token.Authorization = await context.Set<TAuthorization>()
.FindAsync([ConvertIdentifierFromString(identifier)], cancellationToken);
#else
// Warning: when targeting older TFMs, FindAsync() is deliberately not used to work around a breaking
// change introduced in Entity Framework Core 3.x (where a ValueTask instead of a Task is now returned).
@ -1040,12 +1126,12 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new InvalidOperationException(SR.GetResourceString(SR.ID0251));
TAuthorization? GetTrackedEntity() =>
(from entry in Context.ChangeTracker.Entries<TAuthorization>()
(from entry in context.ChangeTracker.Entries<TAuthorization>()
where entry.Entity.Id is TKey identifier && identifier.Equals(key)
select entry.Entity).FirstOrDefault();
Task<TAuthorization?> QueryAsync() =>
(from authorization in Authorizations.AsTracking()
(from authorization in context.Set<TAuthorization>().AsTracking()
where authorization.Id!.Equals(key)
select authorization).FirstOrDefaultAsync(cancellationToken);
#endif
@ -1056,7 +1142,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
// If the authorization is not attached to the token, try to load it manually.
if (token.Authorization is null)
{
var reference = Context.Entry(token).Reference(entry => entry.Authorization);
var reference = context.Entry(token).Reference(entry => entry.Authorization);
if (reference.EntityEntry.State is EntityState.Detached)
{
return;
@ -1220,23 +1306,25 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
throw new ArgumentNullException(nameof(token));
}
Context.Attach(token);
var context = await Context.GetDbContextAsync(cancellationToken);
context.Attach(token);
// Generate a new concurrency token and attach it
// to the token before persisting the changes.
token.ConcurrencyToken = Guid.NewGuid().ToString();
Context.Update(token);
context.Update(token);
try
{
await Context.SaveChangesAsync(cancellationToken);
await context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
Context.Entry(token).State = EntityState.Unchanged;
context.Entry(token).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0247), exception);
}
@ -1254,7 +1342,11 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
return default;
}
return (TKey?) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
return (TKey?) GetConverter().ConvertFromInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
/// <summary>
@ -1269,6 +1361,10 @@ public class OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuth
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
return GetConverter().ConvertToInvariantString(identifier);
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "Only primitive types are supported as entity keys.")]
static TypeConverter GetConverter() => TypeDescriptor.GetConverter(typeof(TKey));
}
}

38
src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs

@ -5,6 +5,8 @@
*/
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenIddict.Core;
using OpenIddict.MongoDb;
using OpenIddict.MongoDb.Models;
@ -51,10 +53,15 @@ public sealed class OpenIddictMongoDbBuilder
/// Configures OpenIddict to use the specified entity as the default application entity.
/// </summary>
/// <returns>The <see cref="OpenIddictMongoDbBuilder"/> instance.</returns>
public OpenIddictMongoDbBuilder ReplaceDefaultApplicationEntity<TApplication>()
public OpenIddictMongoDbBuilder ReplaceDefaultApplicationEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication>()
where TApplication : OpenIddictMongoDbApplication
{
Services.Configure<OpenIddictCoreOptions>(options => options.DefaultApplicationType = typeof(TApplication));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictApplicationManager>(static provider =>
provider.GetRequiredService<OpenIddictApplicationManager<TApplication>>()));
Services.Replace(ServiceDescriptor.Scoped<
IOpenIddictApplicationStore<TApplication>, OpenIddictMongoDbApplicationStore<TApplication>>());
return this;
}
@ -63,10 +70,15 @@ public sealed class OpenIddictMongoDbBuilder
/// Configures OpenIddict to use the specified entity as the default authorization entity.
/// </summary>
/// <returns>The <see cref="OpenIddictMongoDbBuilder"/> instance.</returns>
public OpenIddictMongoDbBuilder ReplaceDefaultAuthorizationEntity<TAuthorization>()
public OpenIddictMongoDbBuilder ReplaceDefaultAuthorizationEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization>()
where TAuthorization : OpenIddictMongoDbAuthorization
{
Services.Configure<OpenIddictCoreOptions>(options => options.DefaultAuthorizationType = typeof(TAuthorization));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictAuthorizationManager>(static provider =>
provider.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>()));
Services.Replace(ServiceDescriptor.Scoped<
IOpenIddictAuthorizationStore<TAuthorization>, OpenIddictMongoDbAuthorizationStore<TAuthorization>>());
return this;
}
@ -75,10 +87,15 @@ public sealed class OpenIddictMongoDbBuilder
/// Configures OpenIddict to use the specified entity as the default scope entity.
/// </summary>
/// <returns>The <see cref="OpenIddictMongoDbBuilder"/> instance.</returns>
public OpenIddictMongoDbBuilder ReplaceDefaultScopeEntity<TScope>()
public OpenIddictMongoDbBuilder ReplaceDefaultScopeEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope>()
where TScope : OpenIddictMongoDbScope
{
Services.Configure<OpenIddictCoreOptions>(options => options.DefaultScopeType = typeof(TScope));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeManager>(static provider =>
provider.GetRequiredService<OpenIddictScopeManager<TScope>>()));
Services.Replace(ServiceDescriptor.Scoped<
IOpenIddictScopeStore<TScope>, OpenIddictMongoDbScopeStore<TScope>>());
return this;
}
@ -87,10 +104,15 @@ public sealed class OpenIddictMongoDbBuilder
/// Configures OpenIddict to use the specified entity as the default token entity.
/// </summary>
/// <returns>The <see cref="OpenIddictMongoDbBuilder"/> instance.</returns>
public OpenIddictMongoDbBuilder ReplaceDefaultTokenEntity<TToken>()
public OpenIddictMongoDbBuilder ReplaceDefaultTokenEntity<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken>()
where TToken : OpenIddictMongoDbToken
{
Services.Configure<OpenIddictCoreOptions>(options => options.DefaultTokenType = typeof(TToken));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenManager>(static provider =>
provider.GetRequiredService<OpenIddictTokenManager<TToken>>()));
Services.Replace(ServiceDescriptor.Scoped<
IOpenIddictTokenStore<TToken>, OpenIddictMongoDbTokenStore<TToken>>());
return this;
}

13
src/OpenIddict.MongoDb/OpenIddictMongoDbExtensions.cs

@ -40,15 +40,10 @@ public static class OpenIddictMongoDbExtensions
// Note: the Mongo stores/resolvers don't depend on scoped/transient services and thus
// can be safely registered as singleton services and shared/reused across requests.
builder.ReplaceApplicationStoreResolver<OpenIddictMongoDbApplicationStoreResolver>(ServiceLifetime.Singleton)
.ReplaceAuthorizationStoreResolver<OpenIddictMongoDbAuthorizationStoreResolver>(ServiceLifetime.Singleton)
.ReplaceScopeStoreResolver<OpenIddictMongoDbScopeStoreResolver>(ServiceLifetime.Singleton)
.ReplaceTokenStoreResolver<OpenIddictMongoDbTokenStoreResolver>(ServiceLifetime.Singleton);
builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbApplicationStore<>));
builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbAuthorizationStore<>));
builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbScopeStore<>));
builder.Services.TryAddSingleton(typeof(OpenIddictMongoDbTokenStore<>));
builder.ReplaceApplicationStore<OpenIddictMongoDbApplication, OpenIddictMongoDbApplicationStore>(ServiceLifetime.Singleton)
.ReplaceAuthorizationStore<OpenIddictMongoDbAuthorization, OpenIddictMongoDbAuthorizationStore>(ServiceLifetime.Singleton)
.ReplaceScopeStore<OpenIddictMongoDbScope, OpenIddictMongoDbScopeStore>(ServiceLifetime.Singleton)
.ReplaceTokenStore<OpenIddictMongoDbToken, OpenIddictMongoDbTokenStore>(ServiceLifetime.Singleton);
builder.Services.TryAddSingleton<IOpenIddictMongoDbContext, OpenIddictMongoDbContext>();

50
src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbApplicationStoreResolver.cs

@ -1,50 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.MongoDb.Models;
namespace OpenIddict.MongoDb;
/// <summary>
/// Exposes a method allowing to resolve an application store.
/// </summary>
public sealed class OpenIddictMongoDbApplicationStoreResolver : IOpenIddictApplicationStoreResolver
{
private readonly ConcurrentDictionary<Type, Type> _cache = new();
private readonly IServiceProvider _provider;
public OpenIddictMongoDbApplicationStoreResolver(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <summary>
/// Returns an application store compatible with the specified application type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <returns>An <see cref="IOpenIddictApplicationStore{TApplication}"/>.</returns>
public IOpenIddictApplicationStore<TApplication> Get<TApplication>() where TApplication : class
{
var store = _provider.GetService<IOpenIddictApplicationStore<TApplication>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TApplication), key =>
{
if (!typeof(OpenIddictMongoDbApplication).IsAssignableFrom(key))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0257));
}
return typeof(OpenIddictMongoDbApplicationStore<>).MakeGenericType(key);
});
return (IOpenIddictApplicationStore<TApplication>) _provider.GetRequiredService(type);
}
}

50
src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbAuthorizationStoreResolver.cs

@ -1,50 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.MongoDb.Models;
namespace OpenIddict.MongoDb;
/// <summary>
/// Exposes a method allowing to resolve an authorization store.
/// </summary>
public sealed class OpenIddictMongoDbAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver
{
private readonly ConcurrentDictionary<Type, Type> _cache = new();
private readonly IServiceProvider _provider;
public OpenIddictMongoDbAuthorizationStoreResolver(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <summary>
/// Returns an authorization store compatible with the specified authorization type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <returns>An <see cref="IOpenIddictAuthorizationStore{TAuthorization}"/>.</returns>
public IOpenIddictAuthorizationStore<TAuthorization> Get<TAuthorization>() where TAuthorization : class
{
var store = _provider.GetService<IOpenIddictAuthorizationStore<TAuthorization>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TAuthorization), key =>
{
if (!typeof(OpenIddictMongoDbAuthorization).IsAssignableFrom(key))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0258));
}
return typeof(OpenIddictMongoDbAuthorizationStore<>).MakeGenericType(key);
});
return (IOpenIddictAuthorizationStore<TAuthorization>) _provider.GetRequiredService(type);
}
}

50
src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbScopeStoreResolver.cs

@ -1,50 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.MongoDb.Models;
namespace OpenIddict.MongoDb;
/// <summary>
/// Exposes a method allowing to resolve a scope store.
/// </summary>
public sealed class OpenIddictMongoDbScopeStoreResolver : IOpenIddictScopeStoreResolver
{
private readonly ConcurrentDictionary<Type, Type> _cache = new();
private readonly IServiceProvider _provider;
public OpenIddictMongoDbScopeStoreResolver(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <summary>
/// Returns a scope store compatible with the specified scope type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <returns>An <see cref="IOpenIddictScopeStore{TScope}"/>.</returns>
public IOpenIddictScopeStore<TScope> Get<TScope>() where TScope : class
{
var store = _provider.GetService<IOpenIddictScopeStore<TScope>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TScope), key =>
{
if (!typeof(OpenIddictMongoDbScope).IsAssignableFrom(key))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0259));
}
return typeof(OpenIddictMongoDbScopeStore<>).MakeGenericType(key);
});
return (IOpenIddictScopeStore<TScope>) _provider.GetRequiredService(type);
}
}

50
src/OpenIddict.MongoDb/Resolvers/OpenIddictMongoDbTokenStoreResolver.cs

@ -1,50 +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.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.MongoDb.Models;
namespace OpenIddict.MongoDb;
/// <summary>
/// Exposes a method allowing to resolve a token store.
/// </summary>
public sealed class OpenIddictMongoDbTokenStoreResolver : IOpenIddictTokenStoreResolver
{
private readonly ConcurrentDictionary<Type, Type> _cache = new();
private readonly IServiceProvider _provider;
public OpenIddictMongoDbTokenStoreResolver(IServiceProvider provider)
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider));
/// <summary>
/// Returns a token store compatible with the specified token type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <returns>An <see cref="IOpenIddictTokenStore{TToken}"/>.</returns>
public IOpenIddictTokenStore<TToken> Get<TToken>() where TToken : class
{
var store = _provider.GetService<IOpenIddictTokenStore<TToken>>();
if (store is not null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TToken), key =>
{
if (!typeof(OpenIddictMongoDbToken).IsAssignableFrom(key))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0260));
}
return typeof(OpenIddictMongoDbTokenStore<>).MakeGenericType(key);
});
return (IOpenIddictTokenStore<TToken>) _provider.GetRequiredService(type);
}
}

19
src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs

@ -18,11 +18,25 @@ using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.MongoDb;
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
public class OpenIddictMongoDbApplicationStore : OpenIddictMongoDbApplicationStore<OpenIddictMongoDbApplication>
{
public OpenIddictMongoDbApplicationStore(
IOpenIddictMongoDbContext context,
IOptionsMonitor<OpenIddictMongoDbOptions> options)
: base(context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
public class OpenIddictMongoDbApplicationStore<TApplication> : IOpenIddictApplicationStore<TApplication>
public class OpenIddictMongoDbApplicationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictMongoDbApplication
{
public OpenIddictMongoDbApplicationStore(
@ -599,7 +613,8 @@ public class OpenIddictMongoDbApplicationStore<TApplication> : IOpenIddictApplic
throw new ArgumentNullException(nameof(application));
}
application.JsonWebKeySet = set is not null ? BsonDocument.Parse(JsonSerializer.Serialize(set)) : null;
application.JsonWebKeySet = set is not null ? BsonDocument.Parse(
JsonSerializer.Serialize(set, OpenIddictSerializer.Default.JsonWebKeySet)) : null;
return default;
}

17
src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs

@ -5,6 +5,7 @@
*/
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
@ -16,11 +17,25 @@ using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.MongoDb;
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
public class OpenIddictMongoDbAuthorizationStore : OpenIddictMongoDbAuthorizationStore<OpenIddictMongoDbAuthorization>
{
public OpenIddictMongoDbAuthorizationStore(
IOpenIddictMongoDbContext context,
IOptionsMonitor<OpenIddictMongoDbOptions> options)
: base(context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
public class OpenIddictMongoDbAuthorizationStore<TAuthorization> : IOpenIddictAuthorizationStore<TAuthorization>
public class OpenIddictMongoDbAuthorizationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictMongoDbAuthorization
{
public OpenIddictMongoDbAuthorizationStore(

17
src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbScopeStore.cs

@ -5,6 +5,7 @@
*/
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
@ -16,11 +17,25 @@ using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.MongoDb;
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
public class OpenIddictMongoDbScopeStore : OpenIddictMongoDbScopeStore<OpenIddictMongoDbScope>
{
public OpenIddictMongoDbScopeStore(
IOpenIddictMongoDbContext context,
IOptionsMonitor<OpenIddictMongoDbOptions> options)
: base(context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
public class OpenIddictMongoDbScopeStore<TScope> : IOpenIddictScopeStore<TScope>
public class OpenIddictMongoDbScopeStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TScope> : IOpenIddictScopeStore<TScope>
where TScope : OpenIddictMongoDbScope
{
public OpenIddictMongoDbScopeStore(

17
src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs

@ -5,6 +5,7 @@
*/
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
@ -16,11 +17,25 @@ using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.MongoDb;
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
public class OpenIddictMongoDbTokenStore : OpenIddictMongoDbTokenStore<OpenIddictMongoDbToken>
{
public OpenIddictMongoDbTokenStore(
IOpenIddictMongoDbContext context,
IOptionsMonitor<OpenIddictMongoDbOptions> options)
: base(context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
public class OpenIddictMongoDbTokenStore<TToken> : IOpenIddictTokenStore<TToken>
public class OpenIddictMongoDbTokenStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictMongoDbToken
{
public OpenIddictMongoDbTokenStore(

4
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs

@ -869,7 +869,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers
/// </summary>
public sealed class EnablePassthroughMode<TContext, TFilter> : IOpenIddictServerHandler<TContext>
where TContext : BaseRequestContext
where TFilter : IOpenIddictServerHandlerFilter<TContext>
where TFilter : class, IOpenIddictServerHandlerFilter<TContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
@ -1252,7 +1252,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers
/// </summary>
public sealed class ProcessPassthroughErrorResponse<TContext, TFilter> : IOpenIddictServerHandler<TContext>
where TContext : BaseRequestContext
where TFilter : IOpenIddictServerHandlerFilter<TContext>
where TFilter : class, IOpenIddictServerHandlerFilter<TContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.

4
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs

@ -928,7 +928,7 @@ public static partial class OpenIddictServerOwinHandlers
/// </summary>
public sealed class EnablePassthroughMode<TContext, TFilter> : IOpenIddictServerHandler<TContext>
where TContext : BaseRequestContext
where TFilter : IOpenIddictServerHandlerFilter<TContext>
where TFilter : class, IOpenIddictServerHandlerFilter<TContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
@ -1431,7 +1431,7 @@ public static partial class OpenIddictServerOwinHandlers
/// </summary>
public sealed class ProcessPassthroughErrorResponse<TContext, TFilter> : IOpenIddictServerHandler<TContext>
where TContext : BaseRequestContext
where TFilter : IOpenIddictServerHandlerFilter<TContext>
where TFilter : class, IOpenIddictServerHandlerFilter<TContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.

98
src/OpenIddict.Server/OpenIddictServerHandlerDescriptor.cs

@ -7,6 +7,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict.Server;
@ -69,34 +70,16 @@ public sealed class OpenIddictServerHandlerDescriptor
/// <summary>
/// Adds the type of a handler filter to the filters list.
/// </summary>
/// <param name="type">The event handler filter type.</param>
/// <typeparam name="TFilter">The event handler filter type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> AddFilter(Type type)
public Builder<TContext> AddFilter<TFilter>()
where TFilter : class, IOpenIddictServerHandlerFilter<TContext>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(IOpenIddictServerHandlerFilter<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0104));
}
_filters.Add(type);
_filters.Add(typeof(TFilter));
return this;
}
/// <summary>
/// Adds the type of a handler filter to the filters list.
/// </summary>
/// <typeparam name="TFilter">The event handler filter type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> AddFilter<TFilter>()
where TFilter : IOpenIddictServerHandlerFilter<TContext>
=> AddFilter(typeof(TFilter));
/// <summary>
/// Imports the properties set on the specified descriptor.
/// </summary>
@ -136,8 +119,7 @@ public sealed class OpenIddictServerHandlerDescriptor
throw new ArgumentNullException(nameof(descriptor));
}
var type = descriptor.ServiceType;
if (!typeof(IOpenIddictServerHandler<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type))
if (!typeof(IOpenIddictServerHandler<TContext>).IsAssignableFrom(descriptor.ServiceType))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0104));
}
@ -182,24 +164,18 @@ public sealed class OpenIddictServerHandlerDescriptor
/// <param name="handler">The handler instance.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseInlineHandler(Func<TContext, ValueTask> handler)
{
if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
}
return UseSingletonHandler(new OpenIddictServerHandler<TContext>(handler));
}
=> UseSingletonHandler(new OpenIddictServerHandler<TContext>(handler ??
throw new ArgumentNullException(nameof(handler))));
/// <summary>
/// Configures the descriptor to use the specified scoped handler.
/// </summary>
/// <typeparam name="THandler">The handler type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseScopedHandler<THandler>()
where THandler : IOpenIddictServerHandler<TContext>
=> SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), typeof(THandler), ServiceLifetime.Scoped));
public Builder<TContext> UseScopedHandler<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>()
where THandler : class, IOpenIddictServerHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Scoped<THandler, THandler>());
/// <summary>
/// Configures the descriptor to use the specified scoped handler.
@ -207,27 +183,20 @@ public sealed class OpenIddictServerHandlerDescriptor
/// <typeparam name="THandler">The handler type.</typeparam>
/// <param name="factory">The factory used to create the handler.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseScopedHandler<THandler>(Func<IServiceProvider, object> factory)
where THandler : IOpenIddictServerHandler<TContext>
{
if (factory is null)
{
throw new ArgumentNullException(nameof(factory));
}
return SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), factory, ServiceLifetime.Scoped));
}
public Builder<TContext> UseScopedHandler<THandler>(Func<IServiceProvider, THandler> factory)
where THandler : class, IOpenIddictServerHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Scoped(factory ??
throw new ArgumentNullException(nameof(factory))));
/// <summary>
/// Configures the descriptor to use the specified singleton handler.
/// </summary>
/// <typeparam name="THandler">The handler type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseSingletonHandler<THandler>()
where THandler : IOpenIddictServerHandler<TContext>
=> SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), typeof(THandler), ServiceLifetime.Singleton));
public Builder<TContext> UseSingletonHandler<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>()
where THandler : class, IOpenIddictServerHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Singleton<THandler, THandler>());
/// <summary>
/// Configures the descriptor to use the specified singleton handler.
@ -235,17 +204,10 @@ public sealed class OpenIddictServerHandlerDescriptor
/// <typeparam name="THandler">The handler type.</typeparam>
/// <param name="factory">The factory used to create the handler.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseSingletonHandler<THandler>(Func<IServiceProvider, object> factory)
where THandler : IOpenIddictServerHandler<TContext>
{
if (factory is null)
{
throw new ArgumentNullException(nameof(factory));
}
return SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), factory, ServiceLifetime.Singleton));
}
public Builder<TContext> UseSingletonHandler<THandler>(Func<IServiceProvider, THandler> factory)
where THandler : class, IOpenIddictServerHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Singleton(factory ??
throw new ArgumentNullException(nameof(factory))));
/// <summary>
/// Configures the descriptor to use the specified singleton handler.
@ -254,15 +216,9 @@ public sealed class OpenIddictServerHandlerDescriptor
/// <param name="handler">The handler instance.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseSingletonHandler<THandler>(THandler handler)
where THandler : IOpenIddictServerHandler<TContext>
{
if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
}
return SetServiceDescriptor(new ServiceDescriptor(typeof(THandler), handler));
}
where THandler : class, IOpenIddictServerHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Singleton(handler ??
throw new ArgumentNullException(nameof(handler))));
/// <summary>
/// Build a new descriptor instance, based on the parameters that were previously set.

16
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -746,7 +746,8 @@ public static partial class OpenIddictServerHandlers
// Note: a unique claim using the special JSON_ARRAY claim value type is allowed
// if the individual elements of the parsed JSON array are all string values.
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize<JsonElement>(value) is { ValueKind: JsonValueKind.Array } element &&
JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.JsonElement)
is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),
// The following claims MUST be represented as unique numeric dates.
@ -2421,7 +2422,8 @@ public static partial class OpenIddictServerHandlers
// Note: a unique claim using the special JSON_ARRAY claim value type is allowed
// if the individual elements of the parsed JSON array are all string values.
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize<JsonElement>(value) is { ValueKind: JsonValueKind.Array } element &&
JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.JsonElement)
is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),
// Note: unlike other claims (e.g "aud"), the "amr" claim MUST be represented as a unique
@ -2432,7 +2434,8 @@ public static partial class OpenIddictServerHandlers
Claims.AuthenticationMethodReference
=> values.TrueForAll(static value => value.ValueType is ClaimValueTypes.String) ||
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize<JsonElement>(value) is { ValueKind: JsonValueKind.Array } element &&
JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.JsonElement)
is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),
// The following claims MUST be represented as unique integers.
@ -3562,8 +3565,11 @@ public static partial class OpenIddictServerHandlers
Parameters.ClientSecret)
select parameter;
principal.SetClaim(Claims.Private.RequestParameters, JsonSerializer.Deserialize<JsonElement>(
JsonSerializer.Serialize(new OpenIddictRequest(parameters))));
principal.SetClaim(Claims.Private.RequestParameters, JsonSerializer.Deserialize(
JsonSerializer.Serialize(
new OpenIddictRequest(parameters),
OpenIddictSerializer.Default.Request),
OpenIddictSerializer.Default.JsonElement));
context.RequestTokenPrincipal = principal;
}

4
src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs

@ -883,8 +883,8 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
{
// Note: ReadFromJsonAsync() automatically validates the content encoding and transparently
// transcodes the response stream if a non-UTF-8 response is returned by the remote server.
context.Transaction.Response = await response.Content.ReadFromJsonAsync<OpenIddictResponse>(
cancellationToken: context.CancellationToken);
context.Transaction.Response = await response.Content.ReadFromJsonAsync(
OpenIddictSerializer.Default.Response, context.CancellationToken);
}
// If an exception is thrown at this stage, this likely means the returned response was not a valid

98
src/OpenIddict.Validation/OpenIddictValidationHandlerDescriptor.cs

@ -7,6 +7,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict.Validation;
@ -69,34 +70,16 @@ public sealed class OpenIddictValidationHandlerDescriptor
/// <summary>
/// Adds the type of a handler filter to the filters list.
/// </summary>
/// <param name="type">The event handler filter type.</param>
/// <typeparam name="TFilter">The event handler filter type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> AddFilter(Type type)
public Builder<TContext> AddFilter<TFilter>()
where TFilter : class, IOpenIddictValidationHandlerFilter<TContext>
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(IOpenIddictValidationHandlerFilter<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0104));
}
_filters.Add(type);
_filters.Add(typeof(TFilter));
return this;
}
/// <summary>
/// Adds the type of a handler filter to the filters list.
/// </summary>
/// <typeparam name="TFilter">The event handler filter type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> AddFilter<TFilter>()
where TFilter : IOpenIddictValidationHandlerFilter<TContext>
=> AddFilter(typeof(TFilter));
/// <summary>
/// Imports the properties set on the specified descriptor.
/// </summary>
@ -136,8 +119,7 @@ public sealed class OpenIddictValidationHandlerDescriptor
throw new ArgumentNullException(nameof(descriptor));
}
var type = descriptor.ServiceType;
if (!typeof(IOpenIddictValidationHandler<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type))
if (!typeof(IOpenIddictValidationHandler<TContext>).IsAssignableFrom(descriptor.ServiceType))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0104));
}
@ -182,24 +164,18 @@ public sealed class OpenIddictValidationHandlerDescriptor
/// <param name="handler">The handler instance.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseInlineHandler(Func<TContext, ValueTask> handler)
{
if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
}
return UseSingletonHandler(new OpenIddictValidationHandler<TContext>(handler));
}
=> UseSingletonHandler(new OpenIddictValidationHandler<TContext>(handler ??
throw new ArgumentNullException(nameof(handler))));
/// <summary>
/// Configures the descriptor to use the specified scoped handler.
/// </summary>
/// <typeparam name="THandler">The handler type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseScopedHandler<THandler>()
where THandler : IOpenIddictValidationHandler<TContext>
=> SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), typeof(THandler), ServiceLifetime.Scoped));
public Builder<TContext> UseScopedHandler<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>()
where THandler : class, IOpenIddictValidationHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Scoped<THandler, THandler>());
/// <summary>
/// Configures the descriptor to use the specified scoped handler.
@ -207,27 +183,20 @@ public sealed class OpenIddictValidationHandlerDescriptor
/// <typeparam name="THandler">The handler type.</typeparam>
/// <param name="factory">The factory used to create the handler.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseScopedHandler<THandler>(Func<IServiceProvider, object> factory)
where THandler : IOpenIddictValidationHandler<TContext>
{
if (factory is null)
{
throw new ArgumentNullException(nameof(factory));
}
return SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), factory, ServiceLifetime.Scoped));
}
public Builder<TContext> UseScopedHandler<THandler>(Func<IServiceProvider, THandler> factory)
where THandler : class, IOpenIddictValidationHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Scoped(factory ??
throw new ArgumentNullException(nameof(factory))));
/// <summary>
/// Configures the descriptor to use the specified singleton handler.
/// </summary>
/// <typeparam name="THandler">The handler type.</typeparam>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseSingletonHandler<THandler>()
where THandler : IOpenIddictValidationHandler<TContext>
=> SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), typeof(THandler), ServiceLifetime.Singleton));
public Builder<TContext> UseSingletonHandler<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>()
where THandler : class, IOpenIddictValidationHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Singleton<THandler, THandler>());
/// <summary>
/// Configures the descriptor to use the specified singleton handler.
@ -235,17 +204,10 @@ public sealed class OpenIddictValidationHandlerDescriptor
/// <typeparam name="THandler">The handler type.</typeparam>
/// <param name="factory">The factory used to create the handler.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseSingletonHandler<THandler>(Func<IServiceProvider, object> factory)
where THandler : IOpenIddictValidationHandler<TContext>
{
if (factory is null)
{
throw new ArgumentNullException(nameof(factory));
}
return SetServiceDescriptor(new ServiceDescriptor(
typeof(THandler), factory, ServiceLifetime.Singleton));
}
public Builder<TContext> UseSingletonHandler<THandler>(Func<IServiceProvider, THandler> factory)
where THandler : class, IOpenIddictValidationHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Singleton(factory ??
throw new ArgumentNullException(nameof(factory))));
/// <summary>
/// Configures the descriptor to use the specified singleton handler.
@ -254,15 +216,9 @@ public sealed class OpenIddictValidationHandlerDescriptor
/// <param name="handler">The handler instance.</param>
/// <returns>The builder instance, so that calls can be easily chained.</returns>
public Builder<TContext> UseSingletonHandler<THandler>(THandler handler)
where THandler : IOpenIddictValidationHandler<TContext>
{
if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
}
return SetServiceDescriptor(new ServiceDescriptor(typeof(THandler), handler));
}
where THandler : class, IOpenIddictValidationHandler<TContext>
=> SetServiceDescriptor(ServiceDescriptor.Singleton(handler ??
throw new ArgumentNullException(nameof(handler))));
/// <summary>
/// Build a new descriptor instance, based on the parameters that were previously set.

469
test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs

@ -40,78 +40,34 @@ public class OpenIddictCoreBuilderTests
}
[Fact]
public void ReplaceApplicationManager_OverridesDefaultOpenGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceApplicationManager(typeof(OpenGenericApplicationManager<>));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenGenericApplicationManager<>) &&
service.ImplementationType == typeof(OpenGenericApplicationManager<>));
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictApplicationManager<>) &&
service.ImplementationType == typeof(OpenGenericApplicationManager<>));
Assert.DoesNotContain(services, service =>
service.ServiceType == typeof(OpenIddictApplicationManager<>) &&
service.ImplementationType == typeof(OpenIddictApplicationManager<>));
}
[Fact]
public void ReplaceApplicationManager_AddsClosedGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceApplicationManager(typeof(ClosedGenericApplicationManager));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(ClosedGenericApplicationManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictApplicationManager<CustomApplication>) &&
service.ImplementationType == typeof(ClosedGenericApplicationManager));
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictApplicationManager<>) &&
service.ImplementationType == typeof(OpenIddictApplicationManager<>));
}
[Fact]
public void ReplaceApplicationStoreResolver_ThrowsAnExceptionForInvalidStoreResolver()
public void ReplaceApplicationManager_ThrowsAnExceptionForClosedSourceManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceApplicationStoreResolver(typeof(object)));
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceApplicationManager(typeof(ClosedGenericApplicationManager)));
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
}
[Fact]
public void ReplaceApplicationStoreResolver_OverridesDefaultResolver()
public void ReplaceApplicationManager_OverridesDefaultOpenGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceApplicationStoreResolver<CustomApplicationStoreResolver>();
var provider = services.BuildServiceProvider();
var resolver = provider.GetRequiredService<IOpenIddictApplicationStoreResolver>();
builder.ReplaceApplicationManager(typeof(OpenGenericApplicationManager<>));
// Assert
Assert.IsType<CustomApplicationStoreResolver>(resolver);
var descriptor = Assert.Single(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(OpenIddictApplicationManager<>));
Assert.Equal(typeof(OpenGenericApplicationManager<>), descriptor.ImplementationType);
}
[Fact]
@ -129,77 +85,34 @@ public class OpenIddictCoreBuilderTests
}
[Fact]
public void ReplaceAuthorizationManager_OverridesDefaultOpenGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceAuthorizationManager(typeof(OpenGenericAuthorizationManager<>));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenGenericAuthorizationManager<>) &&
service.ImplementationType == typeof(OpenGenericAuthorizationManager<>));
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictAuthorizationManager<>) &&
service.ImplementationType == typeof(OpenGenericAuthorizationManager<>));
Assert.DoesNotContain(services, service =>
service.ServiceType == typeof(OpenIddictAuthorizationManager<>) &&
service.ImplementationType == typeof(OpenIddictAuthorizationManager<>));
}
[Fact]
public void ReplaceAuthorizationManager_AddsClosedGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceAuthorizationManager(typeof(ClosedGenericAuthorizationManager));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(ClosedGenericAuthorizationManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictAuthorizationManager<CustomAuthorization>) &&
service.ImplementationType == typeof(ClosedGenericAuthorizationManager));
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictAuthorizationManager<>) &&
service.ImplementationType == typeof(OpenIddictAuthorizationManager<>));
}
[Fact]
public void ReplaceAuthorizationStoreResolver_ThrowsAnExceptionForInvalidStoreResolver()
public void ReplaceAuthorizationManager_ThrowsAnExceptionForClosedSourceManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceAuthorizationStoreResolver(typeof(object)));
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceAuthorizationManager(typeof(ClosedGenericAuthorizationManager)));
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
}
[Fact]
public void ReplaceAuthorizationStoreResolver_OverridesDefaultResolver()
public void ReplaceAuthorizationManager_OverridesDefaultOpenGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceAuthorizationStoreResolver<CustomAuthorizationStoreResolver>();
var provider = services.BuildServiceProvider();
var resolver = provider.GetRequiredService<IOpenIddictAuthorizationStoreResolver>();
builder.ReplaceAuthorizationManager(typeof(OpenGenericAuthorizationManager<>));
// Assert
Assert.IsType<CustomAuthorizationStoreResolver>(resolver);
var descriptor = Assert.Single(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(OpenIddictAuthorizationManager<>));
Assert.Equal(typeof(OpenGenericAuthorizationManager<>), descriptor.ImplementationType);
}
[Fact]
@ -217,78 +130,34 @@ public class OpenIddictCoreBuilderTests
}
[Fact]
public void ReplaceScopeManager_OverridesDefaultOpenGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceScopeManager(typeof(OpenGenericScopeManager<>));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenGenericScopeManager<>) &&
service.ImplementationType == typeof(OpenGenericScopeManager<>));
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictScopeManager<>) &&
service.ImplementationType == typeof(OpenGenericScopeManager<>));
Assert.DoesNotContain(services, service =>
service.ServiceType == typeof(OpenIddictScopeManager<>) &&
service.ImplementationType == typeof(OpenIddictScopeManager<>));
}
[Fact]
public void ReplaceScopeManager_AddsClosedGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceScopeManager(typeof(ClosedGenericScopeManager));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(ClosedGenericScopeManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictScopeManager<CustomScope>) &&
service.ImplementationType == typeof(ClosedGenericScopeManager));
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictScopeManager<>) &&
service.ImplementationType == typeof(OpenIddictScopeManager<>));
}
[Fact]
public void ReplaceScopeStoreResolver_ThrowsAnExceptionForInvalidStoreResolver()
public void ReplaceScopeManager_ThrowsAnExceptionForClosedSourceManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceScopeStoreResolver(typeof(object)));
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceScopeManager(typeof(ClosedGenericScopeManager)));
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
}
[Fact]
public void ReplaceScopeStoreResolver_OverridesDefaultResolver()
public void ReplaceScopeManager_OverridesDefaultOpenGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceScopeStoreResolver<CustomScopeStoreResolver>();
var provider = services.BuildServiceProvider();
var resolver = provider.GetRequiredService<IOpenIddictScopeStoreResolver>();
builder.ReplaceScopeManager(typeof(OpenGenericScopeManager<>));
// Assert
Assert.IsType<CustomScopeStoreResolver>(resolver);
var descriptor = Assert.Single(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(OpenIddictScopeManager<>));
Assert.Equal(typeof(OpenGenericScopeManager<>), descriptor.ImplementationType);
}
[Fact]
@ -306,78 +175,34 @@ public class OpenIddictCoreBuilderTests
}
[Fact]
public void ReplaceTokenManager_OverridesDefaultOpenGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceTokenManager(typeof(OpenGenericTokenManager<>));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenGenericTokenManager<>) &&
service.ImplementationType == typeof(OpenGenericTokenManager<>));
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictTokenManager<>) &&
service.ImplementationType == typeof(OpenGenericTokenManager<>));
Assert.DoesNotContain(services, service =>
service.ServiceType == typeof(OpenIddictTokenManager<>) &&
service.ImplementationType == typeof(OpenIddictTokenManager<>));
}
[Fact]
public void ReplaceTokenManager_AddsClosedGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceTokenManager(typeof(ClosedGenericTokenManager));
// Assert
Assert.Contains(services, service =>
service.ServiceType == typeof(ClosedGenericTokenManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictTokenManager<CustomToken>) &&
service.ImplementationType == typeof(ClosedGenericTokenManager));
Assert.Contains(services, service =>
service.ServiceType == typeof(OpenIddictTokenManager<>) &&
service.ImplementationType == typeof(OpenIddictTokenManager<>));
}
[Fact]
public void ReplaceTokenStoreResolver_ThrowsAnExceptionForInvalidStoreResolver()
public void ReplaceTokenManager_ThrowsAnExceptionForClosedSourceManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceTokenStoreResolver(typeof(object)));
var exception = Assert.Throws<ArgumentException>(() => builder.ReplaceTokenManager(typeof(ClosedGenericTokenManager)));
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
}
[Fact]
public void ReplaceTokenStoreResolver_OverridesDefaultResolver()
public void ReplaceTokenManager_OverridesDefaultOpenGenericManager()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceTokenStoreResolver<CustomTokenStoreResolver>();
var provider = services.BuildServiceProvider();
var resolver = provider.GetRequiredService<IOpenIddictTokenStoreResolver>();
builder.ReplaceTokenManager(typeof(OpenGenericTokenManager<>));
// Assert
Assert.IsType<CustomTokenStoreResolver>(resolver);
var descriptor = Assert.Single(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(OpenIddictTokenManager<>));
Assert.Equal(typeof(OpenGenericTokenManager<>), descriptor.ImplementationType);
}
[Fact]
@ -415,40 +240,7 @@ public class OpenIddictCoreBuilderTests
}
[Fact]
public void SetDefaultApplicationEntity_ThrowsAnExceptionForNullType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(delegate
{
return builder.SetDefaultApplicationEntity(type: null!);
});
Assert.Equal("type", exception.ParamName);
}
[Fact]
public void SetDefaultApplicationEntity_ThrowsAnExceptionForInvalidType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(delegate
{
return builder.SetDefaultApplicationEntity(typeof(long));
});
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
}
[Fact]
public void SetDefaultApplicationEntity_EntityIsCorrectlySet()
public void SetDefaultApplicationEntity_ReplacesUntypedManager()
{
// Arrange
var services = CreateServices();
@ -458,47 +250,14 @@ public class OpenIddictCoreBuilderTests
builder.SetDefaultApplicationEntity<CustomApplication>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType);
}
[Fact]
public void SetDefaultAuthorizationEntity_ThrowsAnExceptionForNullType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(delegate
{
return builder.SetDefaultAuthorizationEntity(type: null!);
});
Assert.Equal("type", exception.ParamName);
}
[Fact]
public void SetDefaultAuthorizationEntity_ThrowsAnExceptionForInvalidType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(delegate
{
return builder.SetDefaultAuthorizationEntity(typeof(long));
});
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictApplicationManager) &&
service.ImplementationFactory is not null);
}
[Fact]
public void SetDefaultAuthorizationEntity_EntityIsCorrectlySet()
public void SetDefaultAuthorizationEntity_ReplacesUntypedManager()
{
// Arrange
var services = CreateServices();
@ -508,47 +267,14 @@ public class OpenIddictCoreBuilderTests
builder.SetDefaultAuthorizationEntity<CustomAuthorization>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType);
}
[Fact]
public void SetDefaultScopeEntity_ThrowsAnExceptionForNullType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(delegate
{
return builder.SetDefaultScopeEntity(type: null!);
});
Assert.Equal("type", exception.ParamName);
}
[Fact]
public void SetDefaultScopeEntity_ThrowsAnExceptionForInvalidType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(delegate
{
return builder.SetDefaultScopeEntity(typeof(long));
});
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictAuthorizationManager) &&
service.ImplementationFactory is not null);
}
[Fact]
public void SetDefaultScopeEntity_EntityIsCorrectlySet()
public void SetDefaultScopeEntity_ReplacesUntypedManager()
{
// Arrange
var services = CreateServices();
@ -558,47 +284,14 @@ public class OpenIddictCoreBuilderTests
builder.SetDefaultScopeEntity<CustomScope>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(CustomScope), options.DefaultScopeType);
}
[Fact]
public void SetDefaultTokenEntity_ThrowsAnExceptionForNullType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(delegate
{
return builder.SetDefaultTokenEntity(type: null!);
});
Assert.Equal("type", exception.ParamName);
}
[Fact]
public void SetDefaultTokenEntity_ThrowsAnExceptionForInvalidType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(delegate
{
return builder.SetDefaultTokenEntity(typeof(long));
});
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictScopeManager) &&
service.ImplementationFactory is not null);
}
[Fact]
public void SetDefaultTokenEntity_EntityIsCorrectlySet()
public void SetDefaultTokenEntity_ReplacesUntypedManager()
{
// Arrange
var services = CreateServices();
@ -608,10 +301,10 @@ public class OpenIddictCoreBuilderTests
builder.SetDefaultTokenEntity<CustomToken>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(CustomToken), options.DefaultTokenType);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenManager) &&
service.ImplementationFactory is not null);
}
[Theory]
@ -651,7 +344,7 @@ public class OpenIddictCoreBuilderTests
private static OpenIddictCoreBuilder CreateBuilder(IServiceCollection services)
=> services.AddOpenIddict().AddCore();
private static IServiceCollection CreateServices()
private static ServiceCollection CreateServices()
{
var services = new ServiceCollection();
services.AddOptions();
@ -664,38 +357,14 @@ public class OpenIddictCoreBuilderTests
private class CustomScope { }
private class CustomToken { }
private class CustomApplicationStoreResolver : IOpenIddictApplicationStoreResolver
{
public IOpenIddictApplicationStore<TApplication> Get<TApplication>() where TApplication : class
=> throw new NotImplementedException();
}
private class CustomAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver
{
public IOpenIddictAuthorizationStore<TAuthorization> Get<TAuthorization>() where TAuthorization : class
=> throw new NotImplementedException();
}
private class CustomScopeStoreResolver : IOpenIddictScopeStoreResolver
{
public IOpenIddictScopeStore<TScope> Get<TScope>() where TScope : class
=> throw new NotImplementedException();
}
private class CustomTokenStoreResolver : IOpenIddictTokenStoreResolver
{
public IOpenIddictTokenStore<TToken> Get<TToken>() where TToken : class
=> throw new NotImplementedException();
}
private class ClosedGenericApplicationManager : OpenIddictApplicationManager<CustomApplication>
{
public ClosedGenericApplicationManager(
IOpenIddictApplicationCache<CustomApplication> cache,
ILogger<OpenIddictApplicationManager<CustomApplication>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictApplicationStoreResolver resolver)
: base(cache, logger, options, resolver)
IOpenIddictApplicationStore<CustomApplication> store)
: base(cache, logger, options, store)
{
}
}
@ -707,8 +376,8 @@ public class OpenIddictCoreBuilderTests
IOpenIddictApplicationCache<TApplication> cache,
ILogger<OpenIddictApplicationManager<TApplication>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictApplicationStoreResolver resolver)
: base(cache, logger, options, resolver)
IOpenIddictApplicationStore<TApplication> store)
: base(cache, logger, options, store)
{
}
}
@ -719,8 +388,8 @@ public class OpenIddictCoreBuilderTests
IOpenIddictAuthorizationCache<CustomAuthorization> cache,
ILogger<OpenIddictAuthorizationManager<CustomAuthorization>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictAuthorizationStoreResolver resolver)
: base(cache, logger, options, resolver)
IOpenIddictAuthorizationStore<CustomAuthorization> store)
: base(cache, logger, options, store)
{
}
}
@ -732,8 +401,8 @@ public class OpenIddictCoreBuilderTests
IOpenIddictAuthorizationCache<TAuthorization> cache,
ILogger<OpenIddictAuthorizationManager<TAuthorization>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictAuthorizationStoreResolver resolver)
: base(cache, logger, options, resolver)
IOpenIddictAuthorizationStore<TAuthorization> store)
: base(cache, logger, options, store)
{
}
}
@ -744,8 +413,8 @@ public class OpenIddictCoreBuilderTests
IOpenIddictScopeCache<CustomScope> cache,
ILogger<OpenIddictScopeManager<CustomScope>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictScopeStoreResolver resolver)
: base(cache, logger, options, resolver)
IOpenIddictScopeStore<CustomScope> store)
: base(cache, logger, options, store)
{
}
}
@ -757,8 +426,8 @@ public class OpenIddictCoreBuilderTests
IOpenIddictScopeCache<TScope> cache,
ILogger<OpenIddictScopeManager<TScope>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictScopeStoreResolver resolver)
: base(cache, logger, options, resolver)
IOpenIddictScopeStore<TScope> store)
: base(cache, logger, options, store)
{
}
}
@ -769,8 +438,8 @@ public class OpenIddictCoreBuilderTests
IOpenIddictTokenCache<CustomToken> cache,
ILogger<OpenIddictTokenManager<CustomToken>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictTokenStoreResolver resolver)
: base(cache, logger, options, resolver)
IOpenIddictTokenStore<CustomToken> store)
: base(cache, logger, options, store)
{
}
}
@ -782,8 +451,8 @@ public class OpenIddictCoreBuilderTests
IOpenIddictTokenCache<TToken> cache,
ILogger<OpenIddictTokenManager<TToken>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictTokenStoreResolver resolver)
: base(cache, logger, options, resolver)
IOpenIddictTokenStore<TToken> store)
: base(cache, logger, options, store)
{
}
}

120
test/OpenIddict.Core.Tests/OpenIddictCoreExtensionsTests.cs

@ -7,7 +7,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests;
@ -85,25 +84,6 @@ public class OpenIddictCoreExtensionsTests
Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type);
}
[Theory]
[InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictApplicationStoreResolver))]
[InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictAuthorizationStoreResolver))]
[InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictScopeStoreResolver))]
[InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictTokenStoreResolver))]
public void AddCore_RegistersDefaultResolvers(Type serviceType, Type implementationType)
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddCore();
// Assert
Assert.Contains(services, service => service.ServiceType == serviceType &&
service.ImplementationType == implementationType);
}
[Theory]
[InlineData(typeof(IOpenIddictApplicationManager))]
[InlineData(typeof(IOpenIddictAuthorizationManager))]
@ -123,7 +103,7 @@ public class OpenIddictCoreExtensionsTests
}
[Fact]
public void AddCore_ResolvingUntypedApplicationManagerThrowsAnExceptionWhenDefaultEntityIsNotSet()
public void AddCore_ResolvingUntypedApplicationManagerThrowsAnException()
{
// Arrange
var services = new ServiceCollection();
@ -137,11 +117,11 @@ public class OpenIddictCoreExtensionsTests
var exception = Assert.Throws<InvalidOperationException>(provider.GetRequiredService<IOpenIddictApplicationManager>);
Assert.Equal(SR.GetResourceString(SR.ID0273), exception.Message);
Assert.Equal(SR.GetResourceString(SR.ID0472), exception.Message);
}
[Fact]
public void AddCore_ResolvingUntypedAuthorizationManagerThrowsAnExceptionWhenDefaultEntityIsNotSet()
public void AddCore_ResolvingUntypedAuthorizationManagerThrowsAnException()
{
// Arrange
var services = new ServiceCollection();
@ -155,11 +135,11 @@ public class OpenIddictCoreExtensionsTests
var exception = Assert.Throws<InvalidOperationException>(provider.GetRequiredService<IOpenIddictAuthorizationManager>);
Assert.Equal(SR.GetResourceString(SR.ID0274), exception.Message);
Assert.Equal(SR.GetResourceString(SR.ID0472), exception.Message);
}
[Fact]
public void AddCore_ResolvingUntypedScopeManagerThrowsAnExceptionWhenDefaultEntityIsNotSet()
public void AddCore_ResolvingUntypedScopeManagerThrowsAnException()
{
// Arrange
var services = new ServiceCollection();
@ -173,11 +153,11 @@ public class OpenIddictCoreExtensionsTests
var exception = Assert.Throws<InvalidOperationException>(provider.GetRequiredService<IOpenIddictScopeManager>);
Assert.Equal(SR.GetResourceString(SR.ID0275), exception.Message);
Assert.Equal(SR.GetResourceString(SR.ID0472), exception.Message);
}
[Fact]
public void AddCore_ResolvingUntypedTokenManagerThrowsAnExceptionWhenDefaultEntityIsNotSet()
public void AddCore_ResolvingUntypedTokenManagerThrowsAnException()
{
// Arrange
var services = new ServiceCollection();
@ -191,91 +171,7 @@ public class OpenIddictCoreExtensionsTests
var exception = Assert.Throws<InvalidOperationException>(provider.GetRequiredService<IOpenIddictTokenManager>);
Assert.Equal(SR.GetResourceString(SR.ID0276), exception.Message);
}
[Fact]
public void AddCore_ResolvingUntypedApplicationManagerReturnsGenericManager()
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddCore(options =>
{
options.SetDefaultApplicationEntity<OpenIddictApplication>();
options.Services.AddSingleton(Mock.Of<IOpenIddictApplicationStore<OpenIddictApplication>>());
});
var provider = services.BuildServiceProvider();
var manager = provider.GetRequiredService<IOpenIddictApplicationManager>();
// Assert
Assert.IsType<OpenIddictApplicationManager<OpenIddictApplication>>(manager);
}
[Fact]
public void AddCore_ResolvingUntypedAuthorizationManagerReturnsGenericManager()
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddCore(options =>
{
options.SetDefaultAuthorizationEntity<OpenIddictAuthorization>();
options.Services.AddSingleton(Mock.Of<IOpenIddictAuthorizationStore<OpenIddictAuthorization>>());
});
var provider = services.BuildServiceProvider();
var manager = provider.GetRequiredService<IOpenIddictAuthorizationManager>();
// Assert
Assert.IsType<OpenIddictAuthorizationManager<OpenIddictAuthorization>>(manager);
}
[Fact]
public void AddCore_ResolvingUntypedScopeManagerReturnsGenericManager()
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddCore(options =>
{
options.SetDefaultScopeEntity<OpenIddictScope>();
options.Services.AddSingleton(Mock.Of<IOpenIddictScopeStore<OpenIddictScope>>());
});
var provider = services.BuildServiceProvider();
var manager = provider.GetRequiredService<IOpenIddictScopeManager>();
// Assert
Assert.IsType<OpenIddictScopeManager<OpenIddictScope>>(manager);
}
[Fact]
public void AddCore_ResolvingUntypedTokenManagerReturnsGenericManager()
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictBuilder(services);
// Act
builder.AddCore(options =>
{
options.SetDefaultTokenEntity<OpenIddictToken>();
options.Services.AddSingleton(Mock.Of<IOpenIddictTokenStore<OpenIddictToken>>());
});
var provider = services.BuildServiceProvider();
var manager = provider.GetRequiredService<IOpenIddictTokenManager>();
// Assert
Assert.IsType<OpenIddictTokenManager<OpenIddictToken>>(manager);
Assert.Equal(SR.GetResourceString(SR.ID0472), exception.Message);
}
public class OpenIddictApplication { }

44
test/OpenIddict.Core.Tests/Resolvers/OpenIddictApplicationStoreResolverTests.cs

@ -1,44 +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 Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests;
public class OpenIddictApplicationStoreResolverTests
{
[Fact]
public void Get_ThrowsAnExceptionWhenStoreCannotBeFound()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictApplicationStoreResolver(provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<OpenIddictApplication>);
Assert.Equal(SR.GetResourceString(SR.ID0228), exception.Message);
}
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictApplicationStore<OpenIddictApplication>>());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictApplicationStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<OpenIddictApplication>());
}
public class OpenIddictApplication { }
}

44
test/OpenIddict.Core.Tests/Resolvers/OpenIddictAuthorizationStoreResolverTests.cs

@ -1,44 +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 Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests;
public class OpenIddictAuthorizationStoreResolverTests
{
[Fact]
public void Get_ThrowsAnExceptionWhenStoreCannotBeFound()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictAuthorizationStoreResolver(provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<OpenIddictAuthorization>);
Assert.Equal(SR.GetResourceString(SR.ID0229), exception.Message);
}
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictAuthorizationStore<OpenIddictAuthorization>>());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictAuthorizationStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<OpenIddictAuthorization>());
}
public class OpenIddictAuthorization { }
}

44
test/OpenIddict.Core.Tests/Resolvers/OpenIddictScopeStoreResolverTests.cs

@ -1,44 +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 Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests;
public class OpenIddictScopeStoreResolverTests
{
[Fact]
public void Get_ThrowsAnExceptionWhenStoreCannotBeFound()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictScopeStoreResolver(provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<OpenIddictScope>);
Assert.Equal(SR.GetResourceString(SR.ID0230), exception.Message);
}
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictScopeStore<OpenIddictScope>>());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictScopeStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<OpenIddictScope>());
}
public class OpenIddictScope { }
}

44
test/OpenIddict.Core.Tests/Resolvers/OpenIddictTokenStoreResolverTests.cs

@ -1,44 +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 Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests;
public class OpenIddictTokenStoreResolverTests
{
[Fact]
public void Get_ThrowsAnExceptionWhenStoreCannotBeFound()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictTokenStoreResolver(provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<OpenIddictToken>);
Assert.Equal(SR.GetResourceString(SR.ID0231), exception.Message);
}
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedType()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictTokenStore<OpenIddictToken>>());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictTokenStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<OpenIddictToken>());
}
public class OpenIddictToken { }
}

86
test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkBuilderTests.cs

@ -6,8 +6,6 @@
using System.Data.Entity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
using OpenIddict.EntityFramework.Models;
using Xunit;
@ -28,7 +26,7 @@ public class OpenIddictEntityFrameworkBuilderTests
}
[Fact]
public void ReplaceDefaultEntities_EntitiesAreCorrectlyReplaced()
public void ReplaceDefaultEntities_StoresAreCorrectlyReplaced()
{
// Arrange
var services = CreateServices();
@ -38,66 +36,26 @@ public class OpenIddictEntityFrameworkBuilderTests
builder.ReplaceDefaultEntities<CustomApplication, CustomAuthorization, CustomScope, CustomToken, long>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType);
Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType);
Assert.Equal(typeof(CustomScope), options.DefaultScopeType);
Assert.Equal(typeof(CustomToken), options.DefaultTokenType);
}
[Fact]
public void UseDbContext_ThrowsAnExceptionForNullType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(delegate
{
return builder.UseDbContext(type: null!);
});
Assert.Equal("type", exception.ParamName);
}
[Fact]
public void UseDbContext_ThrowsAnExceptionForInvalidType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(delegate
{
return builder.UseDbContext(typeof(object));
});
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
}
[Fact]
public void UseDbContext_RegistersDbContextAsScopedService()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.UseDbContext<CustomDbContext>();
// Assert
Assert.Contains(services, service => service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(CustomDbContext) &&
service.ImplementationType == typeof(CustomDbContext));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictApplicationStore<CustomApplication>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkApplicationStore<CustomApplication, CustomAuthorization, CustomToken, long>));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictAuthorizationStore<CustomAuthorization>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkAuthorizationStore<CustomAuthorization, CustomApplication, CustomToken, long>));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictScopeStore<CustomScope>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkScopeStore<CustomScope, long>));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenStore<CustomToken>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkTokenStore<CustomToken, CustomApplication, CustomAuthorization, long>));
}
[Fact]
public void UseDbContext_SetsDbContextTypeInOptions()
public void UseDbContext_OverridesContextType()
{
// Arrange
var services = CreateServices();
@ -107,10 +65,10 @@ public class OpenIddictEntityFrameworkBuilderTests
builder.UseDbContext<CustomDbContext>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>().CurrentValue;
Assert.Equal(typeof(CustomDbContext), options.DbContextType);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictEntityFrameworkContext) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkContext<CustomDbContext>));
}
private static OpenIddictEntityFrameworkBuilder CreateBuilder(IServiceCollection services)

90
test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs

@ -5,8 +5,6 @@
*/
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
using OpenIddict.EntityFramework.Models;
using Xunit;
@ -40,7 +38,7 @@ public class OpenIddictEntityFrameworkExtensionsTests
}
[Fact]
public void UseEntityFramework_RegistersDefaultEntities()
public void UseEntityFramework_RegistersUntypedManagers()
{
// Arrange
var services = new ServiceCollection().AddOptions();
@ -50,59 +48,26 @@ public class OpenIddictEntityFrameworkExtensionsTests
builder.UseEntityFramework();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(OpenIddictEntityFrameworkApplication), options.DefaultApplicationType);
Assert.Equal(typeof(OpenIddictEntityFrameworkAuthorization), options.DefaultAuthorizationType);
Assert.Equal(typeof(OpenIddictEntityFrameworkScope), options.DefaultScopeType);
Assert.Equal(typeof(OpenIddictEntityFrameworkToken), options.DefaultTokenType);
}
[Theory]
[InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictEntityFrameworkApplicationStoreResolver))]
[InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictEntityFrameworkAuthorizationStoreResolver))]
[InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictEntityFrameworkScopeStoreResolver))]
[InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictEntityFrameworkTokenStoreResolver))]
public void UseEntityFramework_RegistersEntityFrameworkStoreResolvers(Type serviceType, Type implementationType)
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictCoreBuilder(services);
// Act
builder.UseEntityFramework();
// Assert
Assert.Contains(services, service => service.ServiceType == serviceType &&
service.ImplementationType == implementationType);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictApplicationManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictAuthorizationManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictScopeManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenManager) &&
service.ImplementationFactory is not null);
}
[Theory]
[InlineData(typeof(OpenIddictEntityFrameworkApplicationStoreResolver.TypeResolutionCache))]
[InlineData(typeof(OpenIddictEntityFrameworkAuthorizationStoreResolver.TypeResolutionCache))]
[InlineData(typeof(OpenIddictEntityFrameworkScopeStoreResolver.TypeResolutionCache))]
[InlineData(typeof(OpenIddictEntityFrameworkTokenStoreResolver.TypeResolutionCache))]
public void UseEntityFramework_RegistersEntityFrameworkStoreResolverCaches(Type type)
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictCoreBuilder(services);
// Act
builder.UseEntityFramework();
// Assert
Assert.Contains(services, service => service.ServiceType == type &&
service.ImplementationType == type);
}
[Theory]
[InlineData(typeof(OpenIddictEntityFrameworkApplicationStore<,,,,>))]
[InlineData(typeof(OpenIddictEntityFrameworkAuthorizationStore<,,,,>))]
[InlineData(typeof(OpenIddictEntityFrameworkScopeStore<,,>))]
[InlineData(typeof(OpenIddictEntityFrameworkTokenStore<,,,,>))]
public void UseEntityFramework_RegistersEntityFrameworkStore(Type type)
[Fact]
public void UseEntityFramework_RegistersEntityFrameworkStores()
{
// Arrange
var services = new ServiceCollection();
@ -112,6 +77,21 @@ public class OpenIddictEntityFrameworkExtensionsTests
builder.UseEntityFramework();
// Assert
Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictApplicationStore<OpenIddictEntityFrameworkApplication>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkApplicationStore));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictAuthorizationStore<OpenIddictEntityFrameworkAuthorization>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkAuthorizationStore));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictScopeStore<OpenIddictEntityFrameworkScope>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkScopeStore));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenStore<OpenIddictEntityFrameworkToken>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkTokenStore));
}
}

105
test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkApplicationStoreResolverTests.cs

@ -1,105 +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.Data.Entity;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using OpenIddict.EntityFramework.Models;
using Xunit;
using static OpenIddict.EntityFramework.OpenIddictEntityFrameworkApplicationStoreResolver;
namespace OpenIddict.EntityFramework.Tests;
public class OpenIddictEntityFrameworkApplicationStoreResolverTests
{
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictApplicationStore<CustomApplication>>());
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
Assert.NotNull(resolver.Get<CustomApplication>());
}
[Fact]
public void Get_ThrowsAnExceptionForInvalidEntityType()
{
// Arrange
var services = new ServiceCollection();
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<CustomApplication>);
Assert.Equal(SR.GetResourceString(SR.ID0234), exception.Message);
}
[Fact]
public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable()
{
// Arrange
var services = new ServiceCollection();
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>(
mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions
{
DbContextType = null
});
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<OpenIddictEntityFrameworkApplication>);
Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message);
}
[Fact]
public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictApplicationStore<CustomApplication>>());
services.AddSingleton(CreateStore());
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>(
mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions
{
DbContextType = typeof(DbContext)
});
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkApplicationStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
Assert.NotNull(resolver.Get<MyApplication>());
}
private static OpenIddictEntityFrameworkApplicationStore<MyApplication, MyAuthorization, MyToken, DbContext, long> CreateStore()
=> new Mock<OpenIddictEntityFrameworkApplicationStore<MyApplication, MyAuthorization, MyToken, DbContext, long>>(
Mock.Of<IMemoryCache>(),
Mock.Of<DbContext>(),
Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>()).Object;
public class CustomApplication { }
public class MyApplication : OpenIddictEntityFrameworkApplication<long, MyAuthorization, MyToken> { }
public class MyAuthorization : OpenIddictEntityFrameworkAuthorization<long, MyApplication, MyToken> { }
public class MyScope : OpenIddictEntityFrameworkScope<long> { }
public class MyToken : OpenIddictEntityFrameworkToken<long, MyApplication, MyAuthorization> { }
}

105
test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkAuthorizationStoreResolverTests.cs

@ -1,105 +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.Data.Entity;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using OpenIddict.EntityFramework.Models;
using Xunit;
using static OpenIddict.EntityFramework.OpenIddictEntityFrameworkAuthorizationStoreResolver;
namespace OpenIddict.EntityFramework.Tests;
public class OpenIddictEntityFrameworkAuthorizationStoreResolverTests
{
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictAuthorizationStore<CustomAuthorization>>());
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
Assert.NotNull(resolver.Get<CustomAuthorization>());
}
[Fact]
public void Get_ThrowsAnExceptionForInvalidEntityType()
{
// Arrange
var services = new ServiceCollection();
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<CustomAuthorization>);
Assert.Equal(SR.GetResourceString(SR.ID0236), exception.Message);
}
[Fact]
public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable()
{
// Arrange
var services = new ServiceCollection();
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>(
mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions
{
DbContextType = null
});
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<OpenIddictEntityFrameworkAuthorization>);
Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message);
}
[Fact]
public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictAuthorizationStore<CustomAuthorization>>());
services.AddSingleton(CreateStore());
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>(
mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions
{
DbContextType = typeof(DbContext)
});
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkAuthorizationStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
Assert.NotNull(resolver.Get<MyAuthorization>());
}
private static OpenIddictEntityFrameworkAuthorizationStore<MyAuthorization, MyApplication, MyToken, DbContext, long> CreateStore()
=> new Mock<OpenIddictEntityFrameworkAuthorizationStore<MyAuthorization, MyApplication, MyToken, DbContext, long>>(
Mock.Of<IMemoryCache>(),
Mock.Of<DbContext>(),
Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>()).Object;
public class CustomAuthorization { }
public class MyApplication : OpenIddictEntityFrameworkApplication<long, MyAuthorization, MyToken> { }
public class MyAuthorization : OpenIddictEntityFrameworkAuthorization<long, MyApplication, MyToken> { }
public class MyScope : OpenIddictEntityFrameworkScope<long> { }
public class MyToken : OpenIddictEntityFrameworkToken<long, MyApplication, MyAuthorization> { }
}

105
test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkScopeStoreResolverTests.cs

@ -1,105 +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.Data.Entity;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using OpenIddict.EntityFramework.Models;
using Xunit;
using static OpenIddict.EntityFramework.OpenIddictEntityFrameworkScopeStoreResolver;
namespace OpenIddict.EntityFramework.Tests;
public class OpenIddictEntityFrameworkScopeStoreResolverTests
{
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictScopeStore<CustomScope>>());
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
Assert.NotNull(resolver.Get<CustomScope>());
}
[Fact]
public void Get_ThrowsAnExceptionForInvalidEntityType()
{
// Arrange
var services = new ServiceCollection();
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<CustomScope>);
Assert.Equal(SR.GetResourceString(SR.ID0237), exception.Message);
}
[Fact]
public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable()
{
// Arrange
var services = new ServiceCollection();
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>(
mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions
{
DbContextType = null
});
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<OpenIddictEntityFrameworkScope>);
Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message);
}
[Fact]
public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictScopeStore<CustomScope>>());
services.AddSingleton(CreateStore());
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>(
mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions
{
DbContextType = typeof(DbContext)
});
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkScopeStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
Assert.NotNull(resolver.Get<MyScope>());
}
private static OpenIddictEntityFrameworkScopeStore<MyScope, DbContext, long> CreateStore()
=> new Mock<OpenIddictEntityFrameworkScopeStore<MyScope, DbContext, long>>(
Mock.Of<IMemoryCache>(),
Mock.Of<DbContext>(),
Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>()).Object;
public class CustomScope { }
public class MyApplication : OpenIddictEntityFrameworkApplication<long, MyAuthorization, MyToken> { }
public class MyAuthorization : OpenIddictEntityFrameworkAuthorization<long, MyApplication, MyToken> { }
public class MyScope : OpenIddictEntityFrameworkScope<long> { }
public class MyToken : OpenIddictEntityFrameworkToken<long, MyApplication, MyAuthorization> { }
}

105
test/OpenIddict.EntityFramework.Tests/Resolvers/OpenIddictEntityFrameworkTokenStoreResolverTests.cs

@ -1,105 +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.Data.Entity;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using OpenIddict.EntityFramework.Models;
using Xunit;
using static OpenIddict.EntityFramework.OpenIddictEntityFrameworkTokenStoreResolver;
namespace OpenIddict.EntityFramework.Tests;
public class OpenIddictEntityFrameworkTokenStoreResolverTests
{
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictTokenStore<CustomToken>>());
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
Assert.NotNull(resolver.Get<CustomToken>());
}
[Fact]
public void Get_ThrowsAnExceptionForInvalidEntityType()
{
// Arrange
var services = new ServiceCollection();
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<CustomToken>);
Assert.Equal(SR.GetResourceString(SR.ID0238), exception.Message);
}
[Fact]
public void Get_ThrowsAnExceptionWhenDbContextTypeIsNotAvailable()
{
// Arrange
var services = new ServiceCollection();
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>(
mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions
{
DbContextType = null
});
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(resolver.Get<OpenIddictEntityFrameworkToken>);
Assert.Equal(SR.GetResourceString(SR.ID0235), exception.Message);
}
[Fact]
public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictTokenStore<CustomToken>>());
services.AddSingleton(CreateStore());
var options = Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>(
mock => mock.CurrentValue == new OpenIddictEntityFrameworkOptions
{
DbContextType = typeof(DbContext)
});
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictEntityFrameworkTokenStoreResolver(new TypeResolutionCache(), options, provider);
// Act and assert
Assert.NotNull(resolver.Get<MyToken>());
}
private static OpenIddictEntityFrameworkTokenStore<MyToken, MyApplication, MyAuthorization, DbContext, long> CreateStore()
=> new Mock<OpenIddictEntityFrameworkTokenStore<MyToken, MyApplication, MyAuthorization, DbContext, long>>(
Mock.Of<IMemoryCache>(),
Mock.Of<DbContext>(),
Mock.Of<IOptionsMonitor<OpenIddictEntityFrameworkOptions>>()).Object;
public class CustomToken { }
public class MyApplication : OpenIddictEntityFrameworkApplication<long, MyAuthorization, MyToken> { }
public class MyAuthorization : OpenIddictEntityFrameworkAuthorization<long, MyApplication, MyToken> { }
public class MyScope : OpenIddictEntityFrameworkScope<long> { }
public class MyToken : OpenIddictEntityFrameworkToken<long, MyApplication, MyAuthorization> { }
}

94
test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreBuilderTests.cs

@ -6,8 +6,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
using OpenIddict.EntityFrameworkCore.Models;
using Xunit;
@ -28,7 +26,7 @@ public class OpenIddictEntityFrameworkCoreBuilderTests
}
[Fact]
public void ReplaceDefaultEntities_EntitiesAreCorrectlyReplaced()
public void ReplaceDefaultEntities_StoresAreCorrectlyReplaced()
{
// Arrange
var services = CreateServices();
@ -38,70 +36,26 @@ public class OpenIddictEntityFrameworkCoreBuilderTests
builder.ReplaceDefaultEntities<CustomApplication, CustomAuthorization, CustomScope, CustomToken, long>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType);
Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType);
Assert.Equal(typeof(CustomScope), options.DefaultScopeType);
Assert.Equal(typeof(CustomToken), options.DefaultTokenType);
}
[Fact]
public void ReplaceDefaultEntities_AllowsSpecifyingCustomKeyType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceDefaultEntities<long>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(OpenIddictEntityFrameworkCoreApplication<long>), options.DefaultApplicationType);
Assert.Equal(typeof(OpenIddictEntityFrameworkCoreAuthorization<long>), options.DefaultAuthorizationType);
Assert.Equal(typeof(OpenIddictEntityFrameworkCoreScope<long>), options.DefaultScopeType);
Assert.Equal(typeof(OpenIddictEntityFrameworkCoreToken<long>), options.DefaultTokenType);
}
[Fact]
public void UseDbContext_ThrowsAnExceptionForNullType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(delegate
{
return builder.UseDbContext(type: null!);
});
Assert.Equal("type", exception.ParamName);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictApplicationStore<CustomApplication>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreApplicationStore<CustomApplication, CustomAuthorization, CustomToken, long>));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictAuthorizationStore<CustomAuthorization>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<CustomAuthorization, CustomApplication, CustomToken, long>));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictScopeStore<CustomScope>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreScopeStore<CustomScope, long>));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenStore<CustomToken>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreTokenStore<CustomToken, CustomApplication, CustomAuthorization, long>));
}
[Fact]
public void UseDbContext_ThrowsAnExceptionForInvalidType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(delegate
{
return builder.UseDbContext(typeof(object));
});
Assert.Equal("type", exception.ParamName);
Assert.StartsWith(SR.GetResourceString(SR.ID0232), exception.Message);
}
[Fact]
public void UseDbContext_SetsDbContextTypeInOptions()
public void UseDbContext_OverridesContextType()
{
// Arrange
var services = CreateServices();
@ -111,10 +65,10 @@ public class OpenIddictEntityFrameworkCoreBuilderTests
builder.UseDbContext<CustomDbContext>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions>>().CurrentValue;
Assert.Equal(typeof(CustomDbContext), options.DbContextType);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictEntityFrameworkCoreContext) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreContext<CustomDbContext>));
}
private static OpenIddictEntityFrameworkCoreBuilder CreateBuilder(IServiceCollection services)
@ -135,9 +89,5 @@ public class OpenIddictEntityFrameworkCoreBuilderTests
public class CustomDbContext : DbContext
{
public CustomDbContext(DbContextOptions options)
: base(options)
{
}
}
}

90
test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs

@ -5,8 +5,6 @@
*/
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
using OpenIddict.EntityFrameworkCore.Models;
using Xunit;
@ -40,7 +38,7 @@ public class OpenIddictEntityFrameworkCoreExtensionsTests
}
[Fact]
public void UseEntityFrameworkCore_RegistersDefaultEntities()
public void UseEntityFrameworkCore_RegistersUntypedManagers()
{
// Arrange
var services = new ServiceCollection().AddOptions();
@ -50,59 +48,26 @@ public class OpenIddictEntityFrameworkCoreExtensionsTests
builder.UseEntityFrameworkCore();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(OpenIddictEntityFrameworkCoreApplication), options.DefaultApplicationType);
Assert.Equal(typeof(OpenIddictEntityFrameworkCoreAuthorization), options.DefaultAuthorizationType);
Assert.Equal(typeof(OpenIddictEntityFrameworkCoreScope), options.DefaultScopeType);
Assert.Equal(typeof(OpenIddictEntityFrameworkCoreToken), options.DefaultTokenType);
}
[Theory]
[InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictEntityFrameworkCoreApplicationStoreResolver))]
[InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictEntityFrameworkCoreAuthorizationStoreResolver))]
[InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictEntityFrameworkCoreScopeStoreResolver))]
[InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictEntityFrameworkCoreTokenStoreResolver))]
public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStoreResolvers(Type serviceType, Type implementationType)
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictCoreBuilder(services);
// Act
builder.UseEntityFrameworkCore();
// Assert
Assert.Contains(services, service => service.ServiceType == serviceType &&
service.ImplementationType == implementationType);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictApplicationManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictAuthorizationManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictScopeManager) &&
service.ImplementationFactory is not null);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenManager) &&
service.ImplementationFactory is not null);
}
[Theory]
[InlineData(typeof(OpenIddictEntityFrameworkCoreApplicationStoreResolver.TypeResolutionCache))]
[InlineData(typeof(OpenIddictEntityFrameworkCoreAuthorizationStoreResolver.TypeResolutionCache))]
[InlineData(typeof(OpenIddictEntityFrameworkCoreScopeStoreResolver.TypeResolutionCache))]
[InlineData(typeof(OpenIddictEntityFrameworkCoreTokenStoreResolver.TypeResolutionCache))]
public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStoreResolverCaches(Type type)
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictCoreBuilder(services);
// Act
builder.UseEntityFrameworkCore();
// Assert
Assert.Contains(services, service => service.ServiceType == type &&
service.ImplementationType == type);
}
[Theory]
[InlineData(typeof(OpenIddictEntityFrameworkCoreApplicationStore<,,,,>))]
[InlineData(typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<,,,,>))]
[InlineData(typeof(OpenIddictEntityFrameworkCoreScopeStore<,,>))]
[InlineData(typeof(OpenIddictEntityFrameworkCoreTokenStore<,,,,>))]
public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStore(Type type)
[Fact]
public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStores()
{
// Arrange
var services = new ServiceCollection();
@ -112,6 +77,21 @@ public class OpenIddictEntityFrameworkCoreExtensionsTests
builder.UseEntityFrameworkCore();
// Assert
Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type);
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictApplicationStore<OpenIddictEntityFrameworkCoreApplication>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreApplicationStore));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictAuthorizationStore<OpenIddictEntityFrameworkCoreAuthorization>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreAuthorizationStore));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictScopeStore<OpenIddictEntityFrameworkCoreScope>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreScopeStore));
Assert.Contains(services, service =>
service.Lifetime == ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenStore<OpenIddictEntityFrameworkCoreToken>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreTokenStore));
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save