Browse Source

Remove OpenIddict.Models/OpenIddict.Stores and move to per-provider models

pull/609/head
Kévin Chalet 8 years ago
parent
commit
32fe4b0ae7
  1. 27
      OpenIddict.sln
  2. 2
      samples/Mvc.Server/Controllers/AuthorizationController.cs
  3. 10
      samples/Mvc.Server/Startup.cs
  4. 2
      src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs
  5. 2
      src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
  6. 2
      src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs
  7. 2
      src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
  8. 5
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  9. 75
      src/OpenIddict.Abstractions/OpenIddictException.cs
  10. 2
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  11. 2
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  12. 2
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  13. 2
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  14. 234
      src/OpenIddict.Core/OpenIddictCoreBuilder.cs
  15. 3
      src/OpenIddict.Core/OpenIddictCoreOptions.cs
  16. 2
      src/OpenIddict.EntityFramework.Models/OpenIddict.EntityFramework.Models.csproj
  17. 105
      src/OpenIddict.EntityFramework.Models/OpenIddictApplication.cs
  18. 77
      src/OpenIddict.EntityFramework.Models/OpenIddictAuthorization.cs
  19. 2
      src/OpenIddict.EntityFramework.Models/OpenIddictScope.cs
  20. 97
      src/OpenIddict.EntityFramework.Models/OpenIddictToken.cs
  21. 2
      src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj
  22. 111
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs
  23. 58
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs
  24. 25
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkOptions.cs
  25. 39
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs
  26. 39
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs
  27. 39
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs
  28. 39
      src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs
  29. 793
      src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
  30. 623
      src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
  31. 534
      src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs
  32. 698
      src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
  33. 15
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddict.EntityFrameworkCore.Models.csproj
  34. 2
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictApplication.cs
  35. 5
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictAuthorization.cs
  36. 69
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictScope.cs
  37. 2
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictToken.cs
  38. 2
      src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj
  39. 123
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs
  40. 6
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs
  41. 61
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs
  42. 25
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.cs
  43. 39
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs
  44. 38
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs
  45. 39
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs
  46. 39
      src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs
  47. 649
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  48. 447
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
  49. 420
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
  50. 578
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
  51. 44
      src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs
  52. 10
      src/OpenIddict.Server/OpenIddictServerOptions.cs
  53. 25
      src/OpenIddict.Stores/OpenIddict.Stores.csproj
  54. 832
      src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs
  55. 694
      src/OpenIddict.Stores/Stores/OpenIddictAuthorizationStore.cs
  56. 577
      src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs
  57. 805
      src/OpenIddict.Stores/Stores/OpenIddictTokenStore.cs
  58. 5
      src/OpenIddict/OpenIddict.csproj
  59. 57
      src/OpenIddict/OpenIddictExtensions.cs
  60. 10
      test/OpenIddict.Core.Tests/OpenIddictCoreBuilderTests.cs
  61. 57
      test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkExtensionsTests.cs
  62. 35
      test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs
  63. 32
      test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs
  64. 34
      test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs
  65. 14
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerInitializerTests.cs
  66. 1
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs
  67. 1
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Exchange.cs
  68. 1
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Introspection.cs
  69. 1
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Revocation.cs
  70. 76
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Serialization.cs
  71. 11
      test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs
  72. 14
      test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
  73. 27
      test/OpenIddict.Tests/OpenIddict.Tests.csproj
  74. 11
      test/OpenIddict.Validation.Tests/OpenIddictValidationHandlerTests.cs

27
OpenIddict.sln

@ -38,25 +38,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.EntityFrameworkC
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Mvc.Tests", "test\OpenIddict.Mvc.Tests\OpenIddict.Mvc.Tests.csproj", "{8B4B0CCC-711B-4F9D-9DE6-DD32BDD3BCCA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Tests", "test\OpenIddict.Tests\OpenIddict.Tests.csproj", "{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Models", "src\OpenIddict.Models\OpenIddict.Models.csproj", "{0102A6CC-41A6-4B34-B49E-65AFE95882BB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.EntityFramework.Models", "src\OpenIddict.EntityFramework.Models\OpenIddict.EntityFramework.Models.csproj", "{0102A6CC-41A6-4B34-B49E-65AFE95882BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.EntityFramework", "src\OpenIddict.EntityFramework\OpenIddict.EntityFramework.csproj", "{BF42CC6C-0B56-4F66-9866-18B8393F3C06}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.EntityFramework.Tests", "test\OpenIddict.EntityFramework.Tests\OpenIddict.EntityFramework.Tests.csproj", "{96325E37-9897-43AC-8408-7B17F58E8788}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Stores", "src\OpenIddict.Stores\OpenIddict.Stores.csproj", "{275D888A-B4C8-4E93-AC4B-B1AA25D98159}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Server", "src\OpenIddict.Server\OpenIddict.Server.csproj", "{21A7F241-CBE7-4F5C-9787-F2C50D135AEA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Server.Tests", "test\OpenIddict.Server.Tests\OpenIddict.Server.Tests.csproj", "{07B02B98-8A68-432D-A932-48E6D52B221A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.Abstractions", "src\OpenIddict.Abstractions\OpenIddict.Abstractions.csproj", "{886A16DA-C9CF-4979-9B38-D06DF8A714B6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Abstractions", "src\OpenIddict.Abstractions\OpenIddict.Abstractions.csproj", "{886A16DA-C9CF-4979-9B38-D06DF8A714B6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.Validation.Tests", "test\OpenIddict.Validation.Tests\OpenIddict.Validation.Tests.csproj", "{F470E734-F4B6-4355-AF32-53412B619E41}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.Validation", "src\OpenIddict.Validation\OpenIddict.Validation.csproj", "{6AB8F9E7-47F8-4A40-837F-C8753362AF54}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Validation", "src\OpenIddict.Validation\OpenIddict.Validation.csproj", "{6AB8F9E7-47F8-4A40-837F-C8753362AF54}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.EntityFrameworkCore.Models", "src\OpenIddict.EntityFrameworkCore.Models\OpenIddict.EntityFrameworkCore.Models.csproj", "{B5371534-4C33-41FA-B3D3-7D70D632DB15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -100,10 +98,6 @@ Global
{8B4B0CCC-711B-4F9D-9DE6-DD32BDD3BCCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B4B0CCC-711B-4F9D-9DE6-DD32BDD3BCCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B4B0CCC-711B-4F9D-9DE6-DD32BDD3BCCA}.Release|Any CPU.Build.0 = Release|Any CPU
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF}.Release|Any CPU.Build.0 = Release|Any CPU
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -116,10 +110,6 @@ Global
{96325E37-9897-43AC-8408-7B17F58E8788}.Debug|Any CPU.Build.0 = Debug|Any CPU
{96325E37-9897-43AC-8408-7B17F58E8788}.Release|Any CPU.ActiveCfg = Release|Any CPU
{96325E37-9897-43AC-8408-7B17F58E8788}.Release|Any CPU.Build.0 = Release|Any CPU
{275D888A-B4C8-4E93-AC4B-B1AA25D98159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{275D888A-B4C8-4E93-AC4B-B1AA25D98159}.Debug|Any CPU.Build.0 = Debug|Any CPU
{275D888A-B4C8-4E93-AC4B-B1AA25D98159}.Release|Any CPU.ActiveCfg = Release|Any CPU
{275D888A-B4C8-4E93-AC4B-B1AA25D98159}.Release|Any CPU.Build.0 = Release|Any CPU
{21A7F241-CBE7-4F5C-9787-F2C50D135AEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21A7F241-CBE7-4F5C-9787-F2C50D135AEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21A7F241-CBE7-4F5C-9787-F2C50D135AEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -140,6 +130,10 @@ Global
{6AB8F9E7-47F8-4A40-837F-C8753362AF54}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AB8F9E7-47F8-4A40-837F-C8753362AF54}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AB8F9E7-47F8-4A40-837F-C8753362AF54}.Release|Any CPU.Build.0 = Release|Any CPU
{B5371534-4C33-41FA-B3D3-7D70D632DB15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5371534-4C33-41FA-B3D3-7D70D632DB15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5371534-4C33-41FA-B3D3-7D70D632DB15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5371534-4C33-41FA-B3D3-7D70D632DB15}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -154,16 +148,15 @@ Global
{A892B3DE-1A02-48D6-993B-DD3DFCAC84C7} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{7831F17A-DF0B-42EC-841B-065A9B5BD786} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{8B4B0CCC-711B-4F9D-9DE6-DD32BDD3BCCA} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{0102A6CC-41A6-4B34-B49E-65AFE95882BB} = {D544447C-D701-46BB-9A5B-C76C612A596B}
{BF42CC6C-0B56-4F66-9866-18B8393F3C06} = {D544447C-D701-46BB-9A5B-C76C612A596B}
{96325E37-9897-43AC-8408-7B17F58E8788} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{275D888A-B4C8-4E93-AC4B-B1AA25D98159} = {D544447C-D701-46BB-9A5B-C76C612A596B}
{21A7F241-CBE7-4F5C-9787-F2C50D135AEA} = {D544447C-D701-46BB-9A5B-C76C612A596B}
{07B02B98-8A68-432D-A932-48E6D52B221A} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{886A16DA-C9CF-4979-9B38-D06DF8A714B6} = {D544447C-D701-46BB-9A5B-C76C612A596B}
{F470E734-F4B6-4355-AF32-53412B619E41} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{6AB8F9E7-47F8-4A40-837F-C8753362AF54} = {D544447C-D701-46BB-9A5B-C76C612A596B}
{B5371534-4C33-41FA-B3D3-7D70D632DB15} = {D544447C-D701-46BB-9A5B-C76C612A596B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A710059F-0466-4D48-9B3A-0EF4F840B616}

2
samples/Mvc.Server/Controllers/AuthorizationController.cs

@ -20,7 +20,7 @@ using Mvc.Server.ViewModels.Authorization;
using Mvc.Server.ViewModels.Shared;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFrameworkCore.Models;
using OpenIddict.Server;
namespace Mvc.Server

10
samples/Mvc.Server/Startup.cs

@ -10,7 +10,7 @@ using Mvc.Server.Models;
using Mvc.Server.Services;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFrameworkCore.Models;
namespace Mvc.Server
{
@ -71,11 +71,9 @@ namespace Mvc.Server
// Register the OpenIddict core services.
.AddCore(options =>
{
// Configure OpenIddict to use the default models.
options.UseDefaultModels();
// Register the Entity Framework stores.
options.AddEntityFrameworkCoreStores<ApplicationDbContext>();
// Configure OpenIddict to use the Entity Framework Core stores and models.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server handler.

2
src/OpenIddict.Abstractions/Managers/IOpenIddictApplicationManager.cs

@ -287,7 +287,7 @@ namespace OpenIddict.Abstractions
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
Task<ImmutableArray<object>> ListAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default);
Task<ImmutableArray<object>> ListAsync([CanBeNull] int? count = null, [CanBeNull] int? offset = null, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.

2
src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs

@ -306,7 +306,7 @@ namespace OpenIddict.Abstractions
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
Task<ImmutableArray<object>> ListAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default);
Task<ImmutableArray<object>> ListAsync([CanBeNull] int? count = null, [CanBeNull] int? offset = null, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.

2
src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs

@ -205,7 +205,7 @@ namespace OpenIddict.Abstractions
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
Task<ImmutableArray<object>> ListAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default);
Task<ImmutableArray<object>> ListAsync([CanBeNull] int? count = null, [CanBeNull] int? offset = null, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.

2
src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs

@ -309,7 +309,7 @@ namespace OpenIddict.Abstractions
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
Task<ImmutableArray<object>> ListAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default);
Task<ImmutableArray<object>> ListAsync([CanBeNull] int? count = null, [CanBeNull] int? offset = null, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.

5
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -39,6 +39,11 @@ namespace OpenIddict.Abstractions
public const string LogoutRequest = "openiddict-logout-request:";
}
public static class Exceptions
{
public const string ConcurrencyError = "concurrency_error";
}
public static class Permissions
{
public static class Endpoints

75
src/OpenIddict.Abstractions/OpenIddictException.cs

@ -0,0 +1,75 @@
using System;
using System.Runtime.Serialization;
namespace OpenIddict.Abstractions
{
/// <summary>
/// Represents an OpenIddict exception.
/// </summary>
public class OpenIddictException : Exception
{
/// <summary>
/// Creates a new <see cref="OpenIddictException"/>.
/// </summary>
/// <param name="reason">The reason of the exception.</param>
/// <param name="message">The exception message.</param>
public OpenIddictException(string reason, string message)
: base(message)
{
Reason = reason;
}
/// <summary>
/// Creates a new <see cref="OpenIddictException"/>.
/// </summary>
/// <param name="reason">The reason of the exception.</param>
/// <param name="message">The exception message.</param>
/// <param name="innerException">The inner exception.</param>
public OpenIddictException(string reason, string message, Exception innerException)
: base(message, innerException)
{
Reason = reason;
}
/// <summary>
/// Creates a new <see cref="OpenIddictException"/>.
/// </summary>
/// <param name="info">
/// The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.
/// </param>
/// <param name="context">
/// The <see cref="StreamingContext"/> that contains contextual information about the source or destination.
/// </param>
protected OpenIddictException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
Reason = info.GetString(nameof(Reason));
}
/// <summary>
/// Gets the reason that caused the exception to be thrown.
/// </summary>
public string Reason { get; }
/// <summary>
/// Serializes the members of this class.
/// </summary>
/// <param name="info">
/// The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.
/// </param>
/// <param name="context">
/// The <see cref="StreamingContext"/> that contains contextual information about the source or destination.
/// </param>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException(nameof(info));
}
info.AddValue(nameof(Reason), Reason);
base.GetObjectData(info, context);
}
}
}

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

@ -764,7 +764,7 @@ namespace OpenIddict.Core
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual Task<ImmutableArray<TApplication>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default)
[CanBeNull] int? count = null, [CanBeNull] int? offset = null, CancellationToken cancellationToken = default)
{
return Store.ListAsync(count, offset, cancellationToken);
}

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

@ -756,7 +756,7 @@ namespace OpenIddict.Core
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual Task<ImmutableArray<TAuthorization>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default)
[CanBeNull] int? count = null, [CanBeNull] int? offset = null, CancellationToken cancellationToken = default)
{
return Store.ListAsync(count, offset, cancellationToken);
}

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

@ -433,7 +433,7 @@ namespace OpenIddict.Core
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual Task<ImmutableArray<TScope>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default)
[CanBeNull] int? count = null, [CanBeNull] int? offset = null, CancellationToken cancellationToken = default)
{
return Store.ListAsync(count, offset, cancellationToken);
}

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

@ -622,7 +622,7 @@ namespace OpenIddict.Core
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual Task<ImmutableArray<TToken>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default)
[CanBeNull] int? count = null, [CanBeNull] int? offset = null, CancellationToken cancellationToken = default)
{
return Store.ListAsync(count, offset, cancellationToken);
}

234
src/OpenIddict.Core/OpenIddictCoreBuilder.cs

@ -63,9 +63,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder AddApplicationStore<TStore>() where TStore : class
=> AddApplicationStore(typeof(TStore));
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
@ -74,8 +76,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder AddApplicationStore([NotNull] Type type)
public OpenIddictCoreBuilder AddApplicationStore(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -97,13 +101,13 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictApplicationStore<>), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStore<>), type, lifetime));
}
else
{
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictApplicationStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
return this;
@ -116,9 +120,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder AddAuthorizationStore<TStore>() where TStore : class
=> AddAuthorizationStore(typeof(TStore));
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
@ -127,8 +133,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder AddAuthorizationStore([NotNull] Type type)
public OpenIddictCoreBuilder AddAuthorizationStore(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -150,13 +158,13 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictAuthorizationStore<>), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStore<>), type, lifetime));
}
else
{
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictAuthorizationStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
return this;
@ -169,9 +177,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder AddScopeStore<TStore>() where TStore : class
=> AddScopeStore(typeof(TStore));
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
@ -180,8 +190,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder AddScopeStore([NotNull] Type type)
public OpenIddictCoreBuilder AddScopeStore(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -203,13 +215,13 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictScopeStore<>), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStore<>), type, lifetime));
}
else
{
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictScopeStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
return this;
@ -222,9 +234,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder AddTokenStore<TStore>() where TStore : class
=> AddTokenStore(typeof(TStore));
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
@ -233,8 +247,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder AddTokenStore([NotNull] Type type)
public OpenIddictCoreBuilder AddTokenStore(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -256,18 +272,31 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictTokenStore<>), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStore<>), type, lifetime));
}
else
{
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictTokenStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStore<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
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.
/// </summary>
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder ReplaceApplicationManager<TManager>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TManager : class
=> ReplaceApplicationManager(typeof(TManager), lifetime);
/// <summary>
/// Replace the default application manager by a custom manager derived
/// from <see cref="OpenIddictApplicationManager{TApplication}"/>.
@ -275,8 +304,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom manager.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder ReplaceApplicationManager([NotNull] Type type)
public OpenIddictCoreBuilder ReplaceApplicationManager(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -298,13 +329,13 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>), type));
Services.Replace(new ServiceDescriptor(typeof(OpenIddictApplicationManager<>), type, lifetime));
}
else
{
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictApplicationManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
Services.Replace(new ServiceDescriptor(typeof(OpenIddictApplicationManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
return this;
@ -314,17 +345,20 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder ReplaceApplicationStoreResolver<TResolver>()
public OpenIddictCoreBuilder ReplaceApplicationStoreResolver<TResolver>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TResolver : IOpenIddictApplicationStoreResolver
=> ReplaceApplicationStoreResolver(typeof(TResolver));
=> ReplaceApplicationStoreResolver(typeof(TResolver), lifetime);
/// <summary>
/// Replaces the default application store resolver by a custom implementation.
/// </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"/>.</returns>
public OpenIddictCoreBuilder ReplaceApplicationStoreResolver([NotNull] Type type)
public OpenIddictCoreBuilder ReplaceApplicationStoreResolver(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -336,22 +370,11 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictApplicationStoreResolver), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictApplicationStoreResolver), type, lifetime));
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.
/// </summary>
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder ReplaceApplicationManager<TManager>() where TManager : class
=> ReplaceApplicationManager(typeof(TManager));
/// <summary>
/// Replace the default authorization manager by a custom manager derived
/// from <see cref="OpenIddictAuthorizationManager{TAuthorization}"/>.
@ -359,9 +382,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// must be either a non-generic or closed generic service.
/// </summary>
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder ReplaceAuthorizationManager<TManager>() where TManager : class
=> ReplaceAuthorizationManager(typeof(TManager));
public OpenIddictCoreBuilder ReplaceAuthorizationManager<TManager>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TManager : class
=> ReplaceAuthorizationManager(typeof(TManager), lifetime);
/// <summary>
/// Replace the default authorization manager by a custom manager derived
@ -370,8 +395,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom manager.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder ReplaceAuthorizationManager([NotNull] Type type)
public OpenIddictCoreBuilder ReplaceAuthorizationManager(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -393,13 +420,13 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>), type));
Services.Replace(new ServiceDescriptor(typeof(OpenIddictAuthorizationManager<>), type, lifetime));
}
else
{
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictAuthorizationManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
Services.Replace(new ServiceDescriptor(typeof(OpenIddictAuthorizationManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
return this;
@ -409,17 +436,20 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver<TResolver>()
public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver<TResolver>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TResolver : IOpenIddictAuthorizationStoreResolver
=> ReplaceAuthorizationStoreResolver(typeof(TResolver));
=> ReplaceAuthorizationStoreResolver(typeof(TResolver), lifetime);
/// <summary>
/// Replaces the default authorization store resolver by a custom implementation.
/// </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"/>.</returns>
public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver([NotNull] Type type)
public OpenIddictCoreBuilder ReplaceAuthorizationStoreResolver(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -431,7 +461,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictAuthorizationStoreResolver), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictAuthorizationStoreResolver), type, lifetime));
return this;
}
@ -444,8 +474,9 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder ReplaceScopeManager<TManager>() where TManager : class
=> ReplaceScopeManager(typeof(TManager));
public OpenIddictCoreBuilder ReplaceScopeManager<TManager>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TManager : class
=> ReplaceScopeManager(typeof(TManager), lifetime);
/// <summary>
/// Replace the default scope manager by a custom manager
@ -454,8 +485,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom manager.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder ReplaceScopeManager([NotNull] Type type)
public OpenIddictCoreBuilder ReplaceScopeManager(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -477,13 +510,13 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>), type));
Services.Replace(new ServiceDescriptor(typeof(OpenIddictScopeManager<>), type, lifetime));
}
else
{
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictScopeManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
Services.Replace(new ServiceDescriptor(typeof(OpenIddictScopeManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
return this;
@ -493,17 +526,20 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder ReplaceScopeStoreResolver<TResolver>()
public OpenIddictCoreBuilder ReplaceScopeStoreResolver<TResolver>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TResolver : IOpenIddictScopeStoreResolver
=> ReplaceScopeStoreResolver(typeof(TResolver));
=> ReplaceScopeStoreResolver(typeof(TResolver), lifetime);
/// <summary>
/// Replaces the default scope store resolver by a custom implementation.
/// </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"/>.</returns>
public OpenIddictCoreBuilder ReplaceScopeStoreResolver([NotNull] Type type)
public OpenIddictCoreBuilder ReplaceScopeStoreResolver(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -515,7 +551,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictScopeStoreResolver), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictScopeStoreResolver), type, lifetime));
return this;
}
@ -527,9 +563,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// must be either a non-generic or closed generic service.
/// </summary>
/// <typeparam name="TManager">The type of the custom manager.</typeparam>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder ReplaceTokenManager<TManager>() where TManager : class
=> ReplaceTokenManager(typeof(TManager));
public OpenIddictCoreBuilder ReplaceTokenManager<TManager>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TManager : class
=> ReplaceTokenManager(typeof(TManager), lifetime);
/// <summary>
/// Replace the default token manager by a custom manager
@ -538,8 +576,10 @@ namespace Microsoft.Extensions.DependencyInjection
/// either a non-generic, a closed or an open generic service.
/// </summary>
/// <param name="type">The type of the custom manager.</param>
/// <param name="lifetime">The lifetime of the registered service.</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder ReplaceTokenManager([NotNull] Type type)
public OpenIddictCoreBuilder ReplaceTokenManager(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -561,13 +601,13 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>), type));
Services.Replace(new ServiceDescriptor(typeof(OpenIddictTokenManager<>), type, lifetime));
}
else
{
Services.Replace(ServiceDescriptor.Scoped(typeof(OpenIddictTokenManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type));
Services.Replace(new ServiceDescriptor(typeof(OpenIddictTokenManager<>)
.MakeGenericType(root.GenericTypeArguments[0]), type, lifetime));
}
return this;
@ -577,17 +617,20 @@ namespace Microsoft.Extensions.DependencyInjection
/// 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"/>.</returns>
public OpenIddictCoreBuilder ReplaceTokenStoreResolver<TResolver>()
public OpenIddictCoreBuilder ReplaceTokenStoreResolver<TResolver>(ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TResolver : IOpenIddictTokenStoreResolver
=> ReplaceTokenStoreResolver(typeof(TResolver));
=> ReplaceTokenStoreResolver(typeof(TResolver), lifetime);
/// <summary>
/// Replaces the default token store resolver by a custom implementation.
/// </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"/>.</returns>
public OpenIddictCoreBuilder ReplaceTokenStoreResolver([NotNull] Type type)
public OpenIddictCoreBuilder ReplaceTokenStoreResolver(
[NotNull] Type type, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
if (type == null)
{
@ -599,30 +642,41 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
Services.Replace(ServiceDescriptor.Scoped(typeof(IOpenIddictTokenStoreResolver), type));
Services.Replace(new ServiceDescriptor(typeof(IOpenIddictTokenStoreResolver), type, lifetime));
return this;
}
/// <summary>
/// Configures OpenIddict to use the specified entities.
/// Configures OpenIddict to use the specified entity as the default application entity.
/// </summary>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder SetDefaultApplicationEntity<TApplication>()
where TApplication : class, new()
=> Configure(options => options.DefaultApplicationType = typeof(TApplication));
/// <summary>
/// Configures OpenIddict to use the specified entity as the default authorization entity.
/// </summary>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder SetDefaultAuthorizationEntity<TAuthorization>()
where TAuthorization : class, new()
=> Configure(options => options.DefaultAuthorizationType = typeof(TAuthorization));
/// <summary>
/// Configures OpenIddict to use the specified entity as the default scope entity.
/// </summary>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder SetDefaultScopeEntity<TScope>()
where TScope : class, new()
=> Configure(options => options.DefaultScopeType = typeof(TScope));
/// <summary>
/// Configures OpenIddict to use the specified entity as the default token entity.
/// </summary>
/// <typeparam name="TApplication">The type corresponding to the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type corresponding to the Authorization entity.</typeparam>
/// <typeparam name="TScope">The type corresponding to the Scope entity.</typeparam>
/// <typeparam name="TToken">The type corresponding to the Token entity.</typeparam>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public OpenIddictCoreBuilder UseCustomModels<TApplication, TAuthorization, TScope, TToken>()
where TApplication : class
where TAuthorization : class
where TScope : class
where TToken : class
=> Configure(options =>
{
options.DefaultApplicationType = typeof(TApplication);
options.DefaultAuthorizationType = typeof(TAuthorization);
options.DefaultScopeType = typeof(TScope);
options.DefaultTokenType = typeof(TToken);
});
public OpenIddictCoreBuilder SetDefaultTokenEntity<TToken>()
where TToken : class, new()
=> Configure(options => options.DefaultTokenType = typeof(TToken));
}
}

3
src/OpenIddict.Core/OpenIddictCoreOptions.cs

@ -8,6 +8,9 @@ using System;
namespace OpenIddict.Core
{
/// <summary>
/// Provides various settings needed to configure the OpenIddict core services.
/// </summary>
public class OpenIddictCoreOptions
{
/// <summary>

2
src/OpenIddict.Models/OpenIddict.Models.csproj → src/OpenIddict.EntityFramework.Models/OpenIddict.EntityFramework.Models.csproj

@ -7,7 +7,7 @@
</PropertyGroup>
<PropertyGroup>
<Description>OpenIddict's default entities, used by the Entity Framework 6.x and Entity Framework Core stores.</Description>
<Description>Relational entities for the Entity Framework 6.x stores.</Description>
<Authors>Kévin Chalet</Authors>
<PackageTags>aspnetcore;authentication;jwt;openidconnect;openiddict;security</PackageTags>
</PropertyGroup>

105
src/OpenIddict.EntityFramework.Models/OpenIddictApplication.cs

@ -0,0 +1,105 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Generic;
namespace OpenIddict.EntityFramework.Models
{
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication : OpenIddictApplication<string, OpenIddictAuthorization, OpenIddictToken>
{
public OpenIddictApplication()
{
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
}
}
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication<TKey, TAuthorization, TToken> where TKey : IEquatable<TKey>
{
/// <summary>
/// Gets the list of the authorizations associated with this application.
/// </summary>
public virtual IList<TAuthorization> Authorizations { get; } = new List<TAuthorization>();
/// <summary>
/// Gets or sets the client identifier
/// associated with the current application.
/// </summary>
public virtual string ClientId { get; set; }
/// <summary>
/// Gets or sets the client secret associated with the current application.
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </summary>
public virtual string ClientSecret { get; set; }
/// <summary>
/// Gets or sets the concurrency token.
/// </summary>
public virtual string ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the consent type
/// associated with the current application.
/// </summary>
public virtual string ConsentType { get; set; }
/// <summary>
/// Gets or sets the display name
/// associated with the current application.
/// </summary>
public virtual string DisplayName { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current application.
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the permissions associated with the
/// current application, serialized as a JSON array.
/// </summary>
public virtual string Permissions { get; set; }
/// <summary>
/// Gets or sets the logout callback URLs associated with
/// the current application, serialized as a JSON array.
/// </summary>
public virtual string PostLogoutRedirectUris { get; set; }
/// <summary>
/// Gets or sets the additional properties serialized as a JSON object,
/// or <c>null</c> if no bag was associated with the current application.
/// </summary>
public virtual string Properties { get; set; }
/// <summary>
/// Gets or sets the callback URLs associated with the
/// current application, serialized as a JSON array.
/// </summary>
public virtual string RedirectUris { get; set; }
/// <summary>
/// Gets the list of the tokens associated with this application.
/// </summary>
public virtual IList<TToken> Tokens { get; } = new List<TToken>();
/// <summary>
/// Gets or sets the application type
/// associated with the current application.
/// </summary>
public virtual string Type { get; set; }
}
}

77
src/OpenIddict.EntityFramework.Models/OpenIddictAuthorization.cs

@ -0,0 +1,77 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Generic;
namespace OpenIddict.EntityFramework.Models
{
/// <summary>
/// Represents an OpenIddict authorization.
/// </summary>
public class OpenIddictAuthorization : OpenIddictAuthorization<string, OpenIddictApplication, OpenIddictToken>
{
public OpenIddictAuthorization()
{
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
}
}
/// <summary>
/// Represents an OpenIddict authorization.
/// </summary>
public class OpenIddictAuthorization<TKey, TApplication, TToken> where TKey : IEquatable<TKey>
{
/// <summary>
/// Gets or sets the application associated with the current authorization.
/// </summary>
public virtual TApplication Application { get; set; }
/// <summary>
/// Gets or sets the concurrency token.
/// </summary>
public virtual string ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current authorization.
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the additional properties serialized as a JSON object,
/// or <c>null</c> if no bag was associated with the current authorization.
/// </summary>
public virtual string Properties { get; set; }
/// <summary>
/// Gets or sets the scopes associated with the current
/// authorization, serialized as a JSON array.
/// </summary>
public virtual string Scopes { get; set; }
/// <summary>
/// Gets or sets the status of the current authorization.
/// </summary>
public virtual string Status { get; set; }
/// <summary>
/// Gets or sets the subject associated with the current authorization.
/// </summary>
public virtual string Subject { get; set; }
/// <summary>
/// Gets the list of tokens associated with the current authorization.
/// </summary>
public virtual IList<TToken> Tokens { get; } = new List<TToken>();
/// <summary>
/// Gets or sets the type of the current authorization.
/// </summary>
public virtual string Type { get; set; }
}
}

2
src/OpenIddict.Models/OpenIddictScope.cs → src/OpenIddict.EntityFramework.Models/OpenIddictScope.cs

@ -6,7 +6,7 @@
using System;
namespace OpenIddict.Models
namespace OpenIddict.EntityFramework.Models
{
/// <summary>
/// Represents an OpenIddict scope.

97
src/OpenIddict.EntityFramework.Models/OpenIddictToken.cs

@ -0,0 +1,97 @@
/*
* 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;
namespace OpenIddict.EntityFramework.Models
{
/// <summary>
/// Represents an OpenIddict token.
/// </summary>
public class OpenIddictToken : OpenIddictToken<string, OpenIddictApplication, OpenIddictAuthorization>
{
public OpenIddictToken()
{
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
}
}
/// <summary>
/// Represents an OpenIddict token.
/// </summary>
public class OpenIddictToken<TKey, TApplication, TAuthorization> where TKey : IEquatable<TKey>
{
/// <summary>
/// Gets or sets the application associated with the current token.
/// </summary>
public virtual TApplication Application { get; set; }
/// <summary>
/// Gets or sets the authorization associated with the current token.
/// </summary>
public virtual TAuthorization Authorization { get; set; }
/// <summary>
/// Gets or sets the concurrency token.
/// </summary>
public virtual string ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the date on which the token
/// will start to be considered valid.
/// </summary>
public virtual DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the date on which the token
/// will no longer be considered valid.
/// </summary>
public virtual DateTimeOffset? ExpirationDate { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current token.
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the payload of the current token, if applicable.
/// Note: this property is only used for reference tokens
/// and may be encrypted for security reasons.
/// </summary>
public virtual string Payload { get; set; }
/// <summary>
/// Gets or sets the additional properties serialized as a JSON object,
/// or <c>null</c> if no bag was associated with the current token.
/// </summary>
public virtual string Properties { get; set; }
/// <summary>
/// Gets or sets the reference identifier associated
/// with the current token, if applicable.
/// Note: this property is only used for reference tokens
/// and may be hashed or encrypted for security reasons.
/// </summary>
public virtual string ReferenceId { get; set; }
/// <summary>
/// Gets or sets the status of the current token.
/// </summary>
public virtual string Status { get; set; }
/// <summary>
/// Gets or sets the subject associated with the current token.
/// </summary>
public virtual string Subject { get; set; }
/// <summary>
/// Gets or sets the type of the current token.
/// </summary>
public virtual string Type { get; set; }
}
}

2
src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj

@ -14,7 +14,7 @@
<ItemGroup>
<ProjectReference Include="..\OpenIddict.Core\OpenIddict.Core.csproj" />
<ProjectReference Include="..\OpenIddict.Stores\OpenIddict.Stores.csproj" />
<ProjectReference Include="..\OpenIddict.EntityFramework.Models\OpenIddict.EntityFramework.Models.csproj" />
</ItemGroup>
<ItemGroup>

111
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs

@ -0,0 +1,111 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.ComponentModel;
using System.Data.Entity;
using JetBrains.Annotations;
using OpenIddict.Core;
using OpenIddict.EntityFramework;
using OpenIddict.EntityFramework.Models;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Exposes the necessary methods required to configure the OpenIddict Entity Framework 6.x services.
/// </summary>
public class OpenIddictEntityFrameworkBuilder
{
/// <summary>
/// Initializes a new instance of <see cref="OpenIddictEntityFrameworkBuilder"/>.
/// </summary>
/// <param name="services">The services collection.</param>
public OpenIddictEntityFrameworkBuilder([NotNull] IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
Services = services;
}
/// <summary>
/// Gets the services collection.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
/// <summary>
/// Amends the default OpenIddict Entity Framework 6.x configuration.
/// </summary>
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictEntityFrameworkBuilder"/>.</returns>
public OpenIddictEntityFrameworkBuilder Configure([NotNull] Action<OpenIddictEntityFrameworkOptions> configuration)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
Services.Configure(configuration);
return this;
}
/// <summary>
/// Configures the OpenIddict Entity Framework 6.x stores to use the specified database context type.
/// </summary>
/// <typeparam name="TContext">The type of the <see cref="DbContext"/> used by OpenIddict.</typeparam>
/// <returns>The <see cref="OpenIddictEntityFrameworkBuilder"/>.</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"/>.</returns>
public OpenIddictEntityFrameworkBuilder UseDbContext([NotNull] Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(DbContext).IsAssignableFrom(type))
{
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
return Configure(options => options.ContextType = type);
}
/// <summary>
/// Configures OpenIddict to use the specified entities, derived
/// from the default OpenIddict Entity Framework 6.x entities.
/// </summary>
/// <returns>The <see cref="OpenIddictEntityFrameworkBuilder"/>.</returns>
public OpenIddictEntityFrameworkBuilder ReplaceDefaultEntities<TApplication, TAuthorization, TScope, TToken, TKey>()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TScope : OpenIddictScope<TKey>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TKey : IEquatable<TKey>
{
Services.Configure<OpenIddictCoreOptions>(options =>
{
options.DefaultApplicationType = typeof(TApplication);
options.DefaultAuthorizationType = typeof(TAuthorization);
options.DefaultScopeType = typeof(TScope);
options.DefaultTokenType = typeof(TToken);
});
return this;
}
}
}

58
src/OpenIddict.EntityFramework/OpenIddictExtensions.cs → src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkExtensions.cs

@ -12,37 +12,69 @@ using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenIddict.EntityFramework;
using OpenIddict.Models;
using OpenIddict.EntityFramework.Models;
namespace Microsoft.Extensions.DependencyInjection
{
public static class OpenIddictExtensions
public static class OpenIddictEntityFrameworkExtensions
{
/// <summary>
/// Registers the Entity Framework 6.x stores. Note: when using the Entity Framework stores,
/// the application <see cref="DbContext"/> MUST be manually registered in the DI container and
/// the entities MUST be derived from the models contained in the OpenIddict.Models package.
/// Registers the Entity Framework 6.x stores services in the DI container and
/// configures OpenIddict to use the Entity Framework 6.x entities by default.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public static OpenIddictCoreBuilder AddEntityFrameworkStores<TContext>([NotNull] this OpenIddictCoreBuilder builder)
where TContext : DbContext
/// <returns>The <see cref="OpenIddictEntityFrameworkBuilder"/>.</returns>
public static OpenIddictEntityFrameworkBuilder UseEntityFramework([NotNull] this OpenIddictCoreBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.SetDefaultApplicationEntity<OpenIddictApplication>()
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>()
.SetDefaultScopeEntity<OpenIddictScope>()
.SetDefaultTokenEntity<OpenIddictToken>();
builder.ReplaceApplicationStoreResolver<OpenIddictApplicationStoreResolver>()
.ReplaceAuthorizationStoreResolver<OpenIddictAuthorizationStoreResolver>()
.ReplaceScopeStoreResolver<OpenIddictScopeStoreResolver>()
.ReplaceTokenStoreResolver<OpenIddictTokenStoreResolver>();
builder.Services.TryAddScoped(typeof(OpenIddictApplicationStore<,,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationStore<,,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictScopeStore<,,>));
builder.Services.TryAddScoped(typeof(OpenIddictTokenStore<,,,,>));
return builder.ReplaceApplicationStoreResolver<OpenIddictApplicationStoreResolver<TContext>>()
.ReplaceAuthorizationStoreResolver<OpenIddictAuthorizationStoreResolver<TContext>>()
.ReplaceScopeStoreResolver<OpenIddictScopeStoreResolver<TContext>>()
.ReplaceTokenStoreResolver<OpenIddictTokenStoreResolver<TContext>>();
return new OpenIddictEntityFrameworkBuilder(builder.Services);
}
/// <summary>
/// Registers the Entity Framework 6.x stores services in the DI container and
/// configures OpenIddict to use the Entity Framework 6.x entities by default.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="configuration">The configuration delegate used to configure the Entity Framework 6.x services.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public static OpenIddictCoreBuilder UseEntityFramework(
[NotNull] this OpenIddictCoreBuilder builder,
[NotNull] Action<OpenIddictEntityFrameworkBuilder> configuration)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
configuration(builder.UseEntityFramework());
return builder;
}
/// <summary>
@ -77,7 +109,7 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(builder));
}
// Note: unlike Entity Framework Core 1.x/2.x, Entity Framework 6.x
// Note: unlike Entity Framework 6.x 1.x/2.x, Entity Framework 6.x
// always throws an exception when using generic types as entity types.
// To ensure a better exception is thrown, a manual check is made here.
if (typeof(TApplication).IsGenericType)

25
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkOptions.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;
using System.Data.Entity;
namespace OpenIddict.EntityFramework
{
/// <summary>
/// Provides various settings needed to configure
/// the OpenIddict Entity Framework 6.x integration.
/// </summary>
public 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 ContextType { get; set; }
}
}

39
src/OpenIddict.EntityFramework/Resolvers/OpenIddictApplicationStoreResolver.cs

@ -1,26 +1,35 @@
using System;
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Concurrent;
using System.Data.Entity;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework
{
/// <summary>
/// Exposes a method allowing to resolve an application store.
/// </summary>
public class OpenIddictApplicationStoreResolver<TContext> : IOpenIddictApplicationStoreResolver
where TContext : DbContext
public class OpenIddictApplicationStoreResolver : IOpenIddictApplicationStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IOptionsMonitor<OpenIddictEntityFrameworkOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictApplicationStoreResolver([NotNull] IServiceProvider provider)
public OpenIddictApplicationStoreResolver(
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkOptions> options,
[NotNull] IServiceProvider provider)
{
_options = options;
_provider = provider;
}
@ -45,9 +54,19 @@ namespace OpenIddict.EntityFramework
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified application type is not compatible with the Entity Framework 6.x stores.")
.Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in generic ")
.Append("'OpenIddictApplication' entity (from the 'OpenIddict.Models' package) or a custom entity ")
.Append("that inherits from the generic 'OpenIddictApplication' entity.")
.Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in ")
.Append("'OpenIddictApplication' entity (from the 'OpenIddict.EntityFramework.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictApplication' entity.")
.ToString());
}
var context = _options.CurrentValue.ContextType;
if (context == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("No Entity Framework 6.x context was specified in the OpenIddict options.")
.Append("To configure the OpenIddict Entity Framework 6.x stores to use a specific 'DbContext', ")
.Append("use 'options.AddEntityFrameworkStores().UseContext<TContext>()'.")
.ToString());
}
@ -55,7 +74,7 @@ namespace OpenIddict.EntityFramework
/* TApplication: */ key,
/* TAuthorization: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TContext: */ typeof(TContext),
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});

39
src/OpenIddict.EntityFramework/Resolvers/OpenIddictAuthorizationStoreResolver.cs

@ -1,26 +1,35 @@
using System;
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Concurrent;
using System.Data.Entity;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework
{
/// <summary>
/// Exposes a method allowing to resolve an authorization store.
/// </summary>
public class OpenIddictAuthorizationStoreResolver<TContext> : IOpenIddictAuthorizationStoreResolver
where TContext : DbContext
public class OpenIddictAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IOptionsMonitor<OpenIddictEntityFrameworkOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictAuthorizationStoreResolver([NotNull] IServiceProvider provider)
public OpenIddictAuthorizationStoreResolver(
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkOptions> options,
[NotNull] IServiceProvider provider)
{
_options = options;
_provider = provider;
}
@ -45,9 +54,19 @@ namespace OpenIddict.EntityFramework
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified authorization type is not compatible with the Entity Framework 6.x stores.")
.Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in generic ")
.Append("'OpenIddictAuthorization' entity (from the 'OpenIddict.Models' package) or a custom entity ")
.Append("that inherits from the generic 'OpenIddictAuthorization' entity.")
.Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in ")
.Append("'OpenIddictAuthorization' entity (from the 'OpenIddict.EntityFramework.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictAuthorization' entity.")
.ToString());
}
var context = _options.CurrentValue.ContextType;
if (context == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("No Entity Framework 6.x context was specified in the OpenIddict options.")
.Append("To configure the OpenIddict Entity Framework 6.x stores to use a specific 'DbContext', ")
.Append("use 'options.AddEntityFrameworkStores().UseContext<TContext>()'.")
.ToString());
}
@ -55,7 +74,7 @@ namespace OpenIddict.EntityFramework
/* TAuthorization: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TContext: */ typeof(TContext),
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});

39
src/OpenIddict.EntityFramework/Resolvers/OpenIddictScopeStoreResolver.cs

@ -1,26 +1,35 @@
using System;
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Concurrent;
using System.Data.Entity;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework
{
/// <summary>
/// Exposes a method allowing to resolve a scope store.
/// </summary>
public class OpenIddictScopeStoreResolver<TContext> : IOpenIddictScopeStoreResolver
where TContext : DbContext
public class OpenIddictScopeStoreResolver : IOpenIddictScopeStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IOptionsMonitor<OpenIddictEntityFrameworkOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictScopeStoreResolver([NotNull] IServiceProvider provider)
public OpenIddictScopeStoreResolver(
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkOptions> options,
[NotNull] IServiceProvider provider)
{
_options = options;
_provider = provider;
}
@ -45,15 +54,25 @@ namespace OpenIddict.EntityFramework
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified scope type is not compatible with the Entity Framework 6.x stores.")
.Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in generic ")
.Append("'OpenIdScope' entity (from the 'OpenIddict.Models' package) or a custom entity ")
.Append("that inherits from the generic 'OpenIddictScope' entity.")
.Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in ")
.Append("'OpenIdScope' entity (from the 'OpenIddict.EntityFramework.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictScope' entity.")
.ToString());
}
var context = _options.CurrentValue.ContextType;
if (context == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("No Entity Framework 6.x context was specified in the OpenIddict options.")
.Append("To configure the OpenIddict Entity Framework 6.x stores to use a specific 'DbContext', ")
.Append("use 'options.AddEntityFrameworkStores().UseContext<TContext>()'.")
.ToString());
}
return typeof(OpenIddictScopeStore<,,>).MakeGenericType(
/* TScope: */ key,
/* TContext: */ typeof(TContext),
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});

39
src/OpenIddict.EntityFramework/Resolvers/OpenIddictTokenStoreResolver.cs

@ -1,26 +1,35 @@
using System;
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Concurrent;
using System.Data.Entity;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework
{
/// <summary>
/// Exposes a method allowing to resolve a token store.
/// </summary>
public class OpenIddictTokenStoreResolver<TContext> : IOpenIddictTokenStoreResolver
where TContext : DbContext
public class OpenIddictTokenStoreResolver : IOpenIddictTokenStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IOptionsMonitor<OpenIddictEntityFrameworkOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictTokenStoreResolver([NotNull] IServiceProvider provider)
public OpenIddictTokenStoreResolver(
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkOptions> options,
[NotNull] IServiceProvider provider)
{
_options = options;
_provider = provider;
}
@ -45,9 +54,19 @@ namespace OpenIddict.EntityFramework
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified token type is not compatible with the Entity Framework 6.x stores.")
.Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in generic ")
.Append("'OpenIddictToken' entity (from the 'OpenIddict.Models' package) or a custom entity ")
.Append("that inherits from the generic 'OpenIddictToken' entity.")
.Append("When enabling the Entity Framework 6.x stores, make sure you use the built-in ")
.Append("'OpenIddictToken' entity (from the 'OpenIddict.EntityFramework.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictToken' entity.")
.ToString());
}
var context = _options.CurrentValue.ContextType;
if (context == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("No Entity Framework 6.x context was specified in the OpenIddict options.")
.Append("To configure the OpenIddict Entity Framework 6.x stores to use a specific 'DbContext', ")
.Append("use 'options.AddEntityFrameworkStores().UseContext<TContext>()'.")
.ToString());
}
@ -55,7 +74,7 @@ namespace OpenIddict.EntityFramework
/* TToken: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TAuthorization: */ root.GenericTypeArguments[2],
/* TContext: */ typeof(TContext),
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});

793
src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs

@ -7,21 +7,25 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using OpenIddict.Models;
using OpenIddict.Stores;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework
{
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictApplicationStore<TContext> : OpenIddictApplicationStore<OpenIddictApplication,
@ -29,77 +33,68 @@ namespace OpenIddict.EntityFramework
OpenIddictToken, TContext, string>
where TContext : DbContext
{
public OpenIddictApplicationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </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 OpenIddictApplicationStore<TContext, TKey> : OpenIddictApplicationStore<OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictToken<TKey>, TContext, TKey>
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
{
}
}
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <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 OpenIddictApplicationStore<TApplication, TAuthorization, TToken, TContext, TKey> :
OpenIddictApplicationStore<TApplication, TAuthorization, TToken, TKey>
public class OpenIddictApplicationStore<TApplication, TAuthorization, TToken, TContext, TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(cache)
public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Cache = cache;
Context = context;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected virtual TContext Context { get; }
protected TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
protected DbSet<TApplication> Applications => Context.Set<TApplication>();
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
protected DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
protected DbSet<TToken> Tokens => Context.Set<TToken>();
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <summary>
/// Determines the number of applications that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
=> Applications.LongCountAsync();
/// <summary>
/// Determines the number of applications that match the specified query.
@ -111,7 +106,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications that match the specified query.
/// </returns>
public override Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TApplication>, IQueryable<TResult>> query, CancellationToken cancellationToken)
public virtual Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TApplication>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
@ -129,7 +124,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -149,7 +144,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -205,9 +200,158 @@ namespace OpenIddict.EntityFramework
Applications.Remove(application);
await Context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
try
{
await Context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The application was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the application from the database and retry the operation.")
.ToString(), exception);
}
}
}
/// <summary>
/// Retrieves an application using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public virtual Task<TApplication> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var key = ConvertIdentifierFromString(identifier);
return (from application in Applications
where application.Id.Equals(key)
select application).FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves an application using its client identifier.
/// </summary>
/// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public virtual Task<TApplication> FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
return (from application in Applications
where application.ClientId == identifier
select application).FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves all the applications associated with the specified post_logout_redirect_uri.
/// </summary>
/// <param name="address">The post_logout_redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
/// </returns>
public virtual async Task<ImmutableArray<TApplication>> FindByPostLogoutRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(address))
{
throw new ArgumentException("The address cannot be null or empty.", nameof(address));
}
// To optimize the efficiency of the query a bit, only applications whose stringified
// PostLogoutRedirectUris contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var applications = await (from application in Applications
where application.PostLogoutRedirectUris.Contains(address)
select application).ToListAsync(cancellationToken);
var builder = ImmutableArray.CreateBuilder<TApplication>();
foreach (var application in applications)
{
foreach (var uri in await GetPostLogoutRedirectUrisAsync(application, cancellationToken))
{
// Note: the post_logout_redirect_uri must be compared
// using case-sensitive "Simple String Comparison".
if (string.Equals(uri, address, StringComparison.Ordinal))
{
builder.Add(application);
break;
}
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Retrieves all the applications associated with the specified redirect_uri.
/// </summary>
/// <param name="address">The redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
/// </returns>
public virtual async Task<ImmutableArray<TApplication>> FindByRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(address))
{
throw new ArgumentException("The address cannot be null or empty.", nameof(address));
}
// To optimize the efficiency of the query a bit, only applications whose stringified
// RedirectUris property contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var applications = await (from application in Applications
where application.RedirectUris.Contains(address)
select application).ToListAsync(cancellationToken);
var builder = ImmutableArray.CreateBuilder<TApplication>();
foreach (var application in applications)
{
foreach (var uri in await GetRedirectUrisAsync(application, cancellationToken))
{
// Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison".
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
if (string.Equals(uri, address, StringComparison.Ordinal))
{
builder.Add(application);
break;
}
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
@ -222,7 +366,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public override Task<TResult> GetAsync<TState, TResult>(
public virtual Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -234,6 +378,296 @@ namespace OpenIddict.EntityFramework
return query(Applications, state).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client identifier associated with the application.
/// </returns>
public virtual ValueTask<string> GetClientIdAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ClientId);
}
/// <summary>
/// Retrieves the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client secret associated with the application.
/// </returns>
public virtual ValueTask<string> GetClientSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ClientSecret);
}
/// <summary>
/// Retrieves the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// </returns>
public virtual ValueTask<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.Type);
}
/// <summary>
/// Retrieves the consent type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the consent type of the application (by default, "explicit").
/// </returns>
public virtual ValueTask<string> GetConsentTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ConsentType);
}
/// <summary>
/// Retrieves the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// </returns>
public virtual ValueTask<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.DisplayName);
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(ConvertIdentifierToString(application.Id));
}
/// <summary>
/// Retrieves the permissions associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the permissions associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetPermissionsAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Permissions))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified permissions is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions);
var permissions = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Permissions)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(permissions);
}
/// <summary>
/// Retrieves the logout callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the post_logout_redirect_uri associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetPostLogoutRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.PostLogoutRedirectUris))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.PostLogoutRedirectUris)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(addresses);
}
/// <summary>
/// Retrieves the additional properties associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the application.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(application.Properties));
}
/// <summary>
/// Retrieves the callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the redirect_uri associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.RedirectUris))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.RedirectUris)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(addresses);
}
/// <summary>
/// Instantiates a new application.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated application, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TApplication> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TApplication>(new TApplication());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TApplication>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Applications.OrderBy(application => application.Id).AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
@ -246,7 +680,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public override async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -258,6 +692,232 @@ namespace OpenIddict.EntityFramework
return ImmutableArray.CreateRange(await query(Applications, state).ToListAsync(cancellationToken));
}
/// <summary>
/// Sets the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientIdAsync([NotNull] TApplication application,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ClientId = identifier;
return Task.CompletedTask;
}
/// <summary>
/// Sets the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientSecretAsync([NotNull] TApplication application,
[CanBeNull] string secret, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ClientSecret = secret;
return Task.CompletedTask;
}
/// <summary>
/// Sets the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientTypeAsync([NotNull] TApplication application,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Sets the consent type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The consent type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetConsentTypeAsync([NotNull] TApplication application,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ConsentType = type;
return Task.CompletedTask;
}
/// <summary>
/// Sets the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="name">The display name associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDisplayNameAsync([NotNull] TApplication application,
[CanBeNull] string name, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.DisplayName = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the permissions associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="permissions">The permissions associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPermissionsAsync([NotNull] TApplication application, ImmutableArray<string> permissions, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (permissions.IsDefaultOrEmpty)
{
application.Permissions = null;
return Task.CompletedTask;
}
application.Permissions = new JArray(permissions.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the logout callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="addresses">The logout callback addresses associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPostLogoutRedirectUrisAsync([NotNull] TApplication application,
ImmutableArray<string> addresses, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (addresses.IsDefaultOrEmpty)
{
application.PostLogoutRedirectUris = null;
return Task.CompletedTask;
}
application.PostLogoutRedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="properties">The additional properties associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (properties == null)
{
application.Properties = null;
return Task.CompletedTask;
}
application.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="addresses">The callback addresses associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetRedirectUrisAsync([NotNull] TApplication application,
ImmutableArray<string> addresses, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (addresses.IsDefaultOrEmpty)
{
application.RedirectUris = null;
return Task.CompletedTask;
}
application.RedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing application.
/// </summary>
@ -266,7 +926,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -281,7 +941,48 @@ namespace OpenIddict.EntityFramework
Context.Entry(application).State = EntityState.Modified;
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The application was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the application from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

623
src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs

@ -7,22 +7,25 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
using OpenIddict.Stores;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework
{
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictAuthorizationStore<TContext> : OpenIddictAuthorizationStore<OpenIddictAuthorization,
@ -30,77 +33,68 @@ namespace OpenIddict.EntityFramework
OpenIddictToken, TContext, string>
where TContext : DbContext
{
public OpenIddictAuthorizationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </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 OpenIddictAuthorizationStore<TContext, TKey> : OpenIddictAuthorizationStore<OpenIddictAuthorization<TKey>,
OpenIddictApplication<TKey>,
OpenIddictToken<TKey>, TContext, TKey>
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
{
}
}
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <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 OpenIddictAuthorizationStore<TAuthorization, TApplication, TToken, TContext, TKey> :
OpenIddictAuthorizationStore<TAuthorization, TApplication, TToken, TKey>
public class OpenIddictAuthorizationStore<TAuthorization, TApplication, TToken, TContext, TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(cache)
public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Cache = cache;
Context = context;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected virtual TContext Context { get; }
protected TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
protected DbSet<TApplication> Applications => Context.Set<TApplication>();
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
protected DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
protected DbSet<TToken> Tokens => Context.Set<TToken>();
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <summary>
/// Determines the number of authorizations that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
=> Authorizations.LongCountAsync();
/// <summary>
/// Determines the number of authorizations that match the specified query.
@ -112,7 +106,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations that match the specified query.
/// </returns>
public override Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TAuthorization>, IQueryable<TResult>> query, CancellationToken cancellationToken)
public virtual Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TAuthorization>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
@ -130,7 +124,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -150,7 +144,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -188,9 +182,189 @@ namespace OpenIddict.EntityFramework
Authorizations.Remove(authorization);
await Context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
try
{
await Context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The authorization was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the authorization from the database and retry the operation.")
.ToString(), exception);
}
}
}
/// <summary>
/// Retrieves the authorizations corresponding to the specified
/// subject and associated with the application identifier.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the subject/client.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
var key = ConvertIdentifierFromString(client);
return ImmutableArray.CreateRange(
await (from authorization in Authorizations.Include(authorization => authorization.Application)
where authorization.Application != null &&
authorization.Application.Id.Equals(key) &&
authorization.Subject == subject
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
var key = ConvertIdentifierFromString(client);
return ImmutableArray.CreateRange(
await (from authorization in Authorizations.Include(authorization => authorization.Application)
where authorization.Application != null &&
authorization.Application.Id.Equals(key) &&
authorization.Subject == subject &&
authorization.Status == status
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="type">The authorization type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
var key = ConvertIdentifierFromString(client);
return ImmutableArray.CreateRange(
await (from authorization in Authorizations.Include(authorization => authorization.Application)
where authorization.Application != null &&
authorization.Application.Id.Equals(key) &&
authorization.Subject == subject &&
authorization.Status == status &&
authorization.Type == type
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier.
/// </returns>
public virtual Task<TAuthorization> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var key = ConvertIdentifierFromString(identifier);
return (from authorization in Authorizations.Include(authorization => authorization.Application)
where authorization.Id.Equals(key)
select authorization).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves all the authorizations corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the specified subject.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindBySubjectAsync(
[NotNull] string subject, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
return ImmutableArray.CreateRange(
await (from authorization in Authorizations.Include(authorization => authorization.Application)
where authorization.Subject == subject
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
@ -202,7 +376,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the authorization.
/// </returns>
public override async ValueTask<string> GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual async ValueTask<string> GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -241,7 +415,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public override Task<TResult> GetAsync<TState, TResult>(
public virtual Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TAuthorization>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -250,7 +424,173 @@ namespace OpenIddict.EntityFramework
throw new ArgumentNullException(nameof(query));
}
return query(Authorizations.Include(authorization => authorization.Application), state).FirstOrDefaultAsync(cancellationToken);
return query(
Authorizations.Include(authorization => authorization.Application), state).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the authorization.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(ConvertIdentifierToString(authorization.Id));
}
/// <summary>
/// Retrieves the additional properties associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the authorization.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (string.IsNullOrEmpty(authorization.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(authorization.Properties));
}
/// <summary>
/// Retrieves the scopes associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes associated with the specified authorization.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetScopesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (string.IsNullOrEmpty(authorization.Scopes))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
return new ValueTask<ImmutableArray<string>>(JArray.Parse(authorization.Scopes).Select(element => (string) element).ToImmutableArray());
}
/// <summary>
/// Retrieves the status associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetStatusAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Status);
}
/// <summary>
/// Retrieves the subject associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetSubjectAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Subject);
}
/// <summary>
/// Retrieves the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the type associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetTypeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Type);
}
/// <summary>
/// Instantiates a new authorization.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated authorization, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TAuthorization> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TAuthorization>(new TAuthorization());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Authorizations.Include(authorization => authorization.Application)
.OrderBy(authorization => authorization.Id)
.AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
@ -265,7 +605,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public override async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TAuthorization>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -285,7 +625,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task PruneAsync(CancellationToken cancellationToken)
public virtual async Task PruneAsync(CancellationToken cancellationToken)
{
// Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
@ -293,14 +633,6 @@ namespace OpenIddict.EntityFramework
IList<Exception> exceptions = null;
IQueryable<TAuthorization> Query(IQueryable<TAuthorization> authorizations, int offset)
=> (from authorization in authorizations.Include(authorization => authorization.Tokens)
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
!authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid))
orderby authorization.Id
select authorization).Skip(offset).Take(1_000);
DbContextTransaction CreateTransaction()
{
// Note: relational providers like Sqlite are known to lack proper support
@ -327,8 +659,15 @@ namespace OpenIddict.EntityFramework
// and thus prevent them from being concurrently modified outside this block.
using (var transaction = CreateTransaction())
{
var authorizations = await ListAsync((source, state) => Query(source, state), offset, cancellationToken);
if (authorizations.IsEmpty)
var authorizations =
await (from authorization in Authorizations.Include(authorization => authorization.Tokens)
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
!authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid))
orderby authorization.Id
select authorization).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
if (authorizations.Count == 0)
{
break;
}
@ -373,7 +712,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task SetApplicationIdAsync([NotNull] TAuthorization authorization,
public virtual async Task SetApplicationIdAsync([NotNull] TAuthorization authorization,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (authorization == null)
@ -410,6 +749,129 @@ namespace OpenIddict.EntityFramework
}
}
/// <summary>
/// Sets the additional properties associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="properties">The additional properties associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (properties == null)
{
authorization.Properties = null;
return Task.CompletedTask;
}
authorization.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the scopes associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="scopes">The scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetScopesAsync([NotNull] TAuthorization authorization,
ImmutableArray<string> scopes, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (scopes.IsDefaultOrEmpty)
{
authorization.Scopes = null;
return Task.CompletedTask;
}
authorization.Scopes = new JArray(scopes.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the status associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="status">The status associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetStatusAsync([NotNull] TAuthorization authorization,
[CanBeNull] string status, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Status = status;
return Task.CompletedTask;
}
/// <summary>
/// Sets the subject associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetSubjectAsync([NotNull] TAuthorization authorization,
[CanBeNull] string subject, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Subject = subject;
return Task.CompletedTask;
}
/// <summary>
/// Sets the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="type">The type associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetTypeAsync([NotNull] TAuthorization authorization,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing authorization.
/// </summary>
@ -418,7 +880,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -433,7 +895,48 @@ namespace OpenIddict.EntityFramework
Context.Entry(authorization).State = EntityState.Modified;
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The authorization was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the authorization from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

534
src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs

@ -6,81 +6,77 @@
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using OpenIddict.Core;
using OpenIddict.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework
{
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictScopeStore<TContext> : OpenIddictScopeStore<OpenIddictScope, TContext, string>
where TContext : DbContext
{
public OpenIddictScopeStore(
[NotNull] TContext context,
[NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </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 OpenIddictScopeStore<TContext, TKey> : OpenIddictScopeStore<OpenIddictScope<TKey>, TContext, TKey>
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictScopeStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
{
}
}
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </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 OpenIddictScopeStore<TScope, TContext, TKey> : Stores.OpenIddictScopeStore<TScope, TKey>
public class OpenIddictScopeStore<TScope, TContext, TKey> : IOpenIddictScopeStore<TScope>
where TScope : OpenIddictScope<TKey>, new()
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictScopeStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(cache)
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Cache = cache;
Context = context;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected virtual TContext Context { get; }
protected TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TScope"/> entity.
/// </summary>
protected DbSet<TScope> Scopes => Context.Set<TScope>();
private DbSet<TScope> Scopes => Context.Set<TScope>();
/// <summary>
/// Determines the number of scopes that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
=> Scopes.LongCountAsync();
/// <summary>
/// Determines the number of scopes that match the specified query.
@ -92,7 +88,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes that match the specified query.
/// </returns>
public override Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken)
public virtual Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
@ -110,7 +106,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual Task CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -130,7 +126,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual async Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -139,7 +135,125 @@ namespace OpenIddict.EntityFramework
Scopes.Remove(scope);
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The scope was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the scope from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Retrieves a scope using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the identifier.
/// </returns>
public virtual Task<TScope> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var key = ConvertIdentifierFromString(identifier);
return (from scope in Scopes
where scope.Id.Equals(key)
select scope).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves a scope using its name.
/// </summary>
/// <param name="name">The name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the specified name.
/// </returns>
public virtual Task<TScope> FindByNameAsync([NotNull] string name, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("The scope name cannot be null or empty.", nameof(name));
}
return (from scope in Scopes
where scope.Name == name
select scope).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves a list of scopes using their name.
/// </summary>
/// <param name="names">The names associated with the scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes corresponding to the specified names.
/// </returns>
public virtual async Task<ImmutableArray<TScope>> FindByNamesAsync(
ImmutableArray<string> names, CancellationToken cancellationToken)
{
if (names.Any(name => string.IsNullOrEmpty(name)))
{
throw new ArgumentException("Scope names cannot be null or empty.", nameof(names));
}
return ImmutableArray.CreateRange(
await (from scope in Scopes
where names.Contains(scope.Name)
select scope).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves all the scopes that contain the specified resource.
/// </summary>
/// <param name="resource">The resource associated with the scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes associated with the specified resource.
/// </returns>
public virtual async Task<ImmutableArray<TScope>> FindByResourceAsync(
[NotNull] string resource, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(resource))
{
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource));
}
// To optimize the efficiency of the query a bit, only scopes whose stringified
// Resources column contains the specified resource are returned. Once the scopes
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var scopes = await (from scope in Scopes
where scope.Resources.Contains(resource)
select scope).ToListAsync(cancellationToken);
var builder = ImmutableArray.CreateBuilder<TScope>();
foreach (var scope in scopes)
{
var resources = await GetResourcesAsync(scope, cancellationToken);
if (resources.Contains(resource, StringComparer.OrdinalIgnoreCase))
{
builder.Add(scope);
}
}
return builder.ToImmutable();
}
/// <summary>
@ -154,7 +268,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public override Task<TResult> GetAsync<TState, TResult>(
public virtual Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -166,6 +280,182 @@ namespace OpenIddict.EntityFramework
return query(Scopes, state).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the description associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the description associated with the specified scope.
/// </returns>
public virtual ValueTask<string> GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.Description);
}
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the scope.
/// </returns>
public virtual ValueTask<string> GetDisplayNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.DisplayName);
}
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the scope.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(ConvertIdentifierToString(scope.Id));
}
/// <summary>
/// Retrieves the name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the name associated with the specified scope.
/// </returns>
public virtual ValueTask<string> GetNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.Name);
}
/// <summary>
/// Retrieves the additional properties associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the scope.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(scope.Properties));
}
/// <summary>
/// Retrieves the resources associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the resources associated with the scope.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetResourcesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Resources))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified resources is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources);
var resources = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(scope.Resources)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(resources);
}
/// <summary>
/// Instantiates a new scope.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated scope, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TScope> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TScope>(new TScope());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TScope>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Scopes.OrderBy(scope => scope.Id).AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
@ -178,7 +468,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public override async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -190,6 +480,125 @@ namespace OpenIddict.EntityFramework
return ImmutableArray.CreateRange(await query(Scopes, state).ToListAsync(cancellationToken));
}
/// <summary>
/// Sets the description associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="description">The description associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.Description = description;
return Task.CompletedTask;
}
/// <summary>
/// Sets the display name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="name">The display name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDisplayNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.DisplayName = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="name">The name associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.Name = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="properties">The additional properties associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (properties == null)
{
scope.Properties = null;
return Task.CompletedTask;
}
scope.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the resources associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="resources">The resources associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetResourcesAsync([NotNull] TScope scope, ImmutableArray<string> resources, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (resources.IsDefaultOrEmpty)
{
scope.Resources = null;
return Task.CompletedTask;
}
scope.Resources = new JArray(resources.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing scope.
/// </summary>
@ -198,7 +607,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -213,7 +622,48 @@ namespace OpenIddict.EntityFramework
Context.Entry(scope).State = EntityState.Modified;
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The scope was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the scope from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

698
src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs

@ -7,22 +7,25 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
using OpenIddict.Stores;
using OpenIddict.EntityFramework.Models;
namespace OpenIddict.EntityFramework
{
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictTokenStore<TContext> : OpenIddictTokenStore<OpenIddictToken,
@ -30,77 +33,68 @@ namespace OpenIddict.EntityFramework
OpenIddictAuthorization, TContext, string>
where TContext : DbContext
{
public OpenIddictTokenStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </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 OpenIddictTokenStore<TContext, TKey> : OpenIddictTokenStore<OpenIddictToken<TKey>,
OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>, TContext, TKey>
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictTokenStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
{
}
}
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <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 OpenIddictTokenStore<TToken, TApplication, TAuthorization, TContext, TKey> :
OpenIddictTokenStore<TToken, TApplication, TAuthorization, TKey>
public class OpenIddictTokenStore<TToken, TApplication, TAuthorization, TContext, TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictTokenStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(cache)
public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Cache = cache;
Context = context;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected virtual TContext Context { get; }
protected TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
protected DbSet<TApplication> Applications => Context.Set<TApplication>();
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
protected DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
protected DbSet<TToken> Tokens => Context.Set<TToken>();
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <summary>
/// Determines the number of tokens that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
=> Tokens.LongCountAsync();
/// <summary>
/// Determines the number of tokens that match the specified query.
@ -112,7 +106,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens that match the specified query.
/// </returns>
public override Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TToken>, IQueryable<TResult>> query, CancellationToken cancellationToken)
public virtual Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TToken>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
@ -130,7 +124,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task CreateAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual Task CreateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -147,8 +141,10 @@ namespace OpenIddict.EntityFramework
/// </summary>
/// <param name="token">The token to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public override Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -157,7 +153,135 @@ namespace OpenIddict.EntityFramework
Tokens.Remove(token);
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The token was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the token from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier.
/// </summary>
/// <param name="identifier">The application identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified application.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindByApplicationIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var key = ConvertIdentifierFromString(identifier);
return ImmutableArray.CreateRange(
await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Application != null &&
token.Application.Id.Equals(key)
select token).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified authorization identifier.
/// </summary>
/// <param name="identifier">The authorization identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified authorization.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindByAuthorizationIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var key = ConvertIdentifierFromString(identifier);
return ImmutableArray.CreateRange(
await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Authorization != null &&
token.Authorization.Id.Equals(key)
select token).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves a token using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier.
/// </returns>
public virtual Task<TToken> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var key = ConvertIdentifierFromString(identifier);
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Id.Equals(key)
select token).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified reference identifier.
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </summary>
/// <param name="identifier">The reference identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified reference identifier.
/// </returns>
public virtual Task<TToken> FindByReferenceIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.ReferenceId == identifier
select token).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified subject.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> FindBySubjectAsync([NotNull] string subject, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
return ImmutableArray.CreateRange(
await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization)
where token.Subject == subject
select token).ToListAsync(cancellationToken));
}
/// <summary>
@ -169,7 +293,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the token.
/// </returns>
public override async ValueTask<string> GetApplicationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual async ValueTask<string> GetApplicationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -208,7 +332,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public override Task<TResult> GetAsync<TState, TResult>(
public virtual Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TToken>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -231,7 +355,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization identifier associated with the token.
/// </returns>
public override async ValueTask<string> GetAuthorizationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual async ValueTask<string> GetAuthorizationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -258,6 +382,226 @@ namespace OpenIddict.EntityFramework
return ConvertIdentifierToString(token.Authorization.Id);
}
/// <summary>
/// Retrieves the creation date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified token.
/// </returns>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<DateTimeOffset?>(token.CreationDate);
}
/// <summary>
/// Retrieves the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the expiration date associated with the specified token.
/// </returns>
public virtual ValueTask<DateTimeOffset?> GetExpirationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<DateTimeOffset?>(token.ExpirationDate);
}
/// <summary>
/// Retrieves the unique identifier associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(ConvertIdentifierToString(token.Id));
}
/// <summary>
/// Retrieves the payload associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the payload associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetPayloadAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Payload);
}
/// <summary>
/// Retrieves the additional properties associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the token.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(token.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(token.Properties));
}
/// <summary>
/// Retrieves the reference identifier associated with a token.
/// Note: depending on the manager used to create the token,
/// the reference identifier may be hashed for security reasons.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the reference identifier associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetReferenceIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.ReferenceId);
}
/// <summary>
/// Retrieves the status associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetStatusAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Status);
}
/// <summary>
/// Retrieves the subject associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetSubjectAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Subject);
}
/// <summary>
/// Retrieves the token type associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token type associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetTokenTypeAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Type);
}
/// <summary>
/// Instantiates a new token.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated token, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TToken> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TToken>(new TToken());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Tokens.Include(token => token.Application)
.Include(token => token.Authorization)
.OrderBy(token => token.Id)
.AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
@ -270,7 +614,7 @@ namespace OpenIddict.EntityFramework
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public override async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TToken>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -291,7 +635,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task PruneAsync(CancellationToken cancellationToken)
public virtual async Task PruneAsync(CancellationToken cancellationToken)
{
// Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
@ -299,13 +643,6 @@ namespace OpenIddict.EntityFramework
IList<Exception> exceptions = null;
IQueryable<TToken> Query(IQueryable<TToken> tokens, int offset)
=> (from token in tokens
where token.ExpirationDate < DateTimeOffset.UtcNow ||
token.Status != OpenIddictConstants.Statuses.Valid
orderby token.Id
select token).Skip(offset).Take(1_000);
DbContextTransaction CreateTransaction()
{
// Note: relational providers like Sqlite are known to lack proper support
@ -332,8 +669,14 @@ namespace OpenIddict.EntityFramework
// and thus prevent them from being concurrently modified outside this block.
using (var transaction = CreateTransaction())
{
var tokens = await ListAsync((source, state) => Query(source, state), offset, cancellationToken);
if (tokens.IsEmpty)
var tokens =
await (from token in Tokens
where token.ExpirationDate < DateTimeOffset.UtcNow ||
token.Status != OpenIddictConstants.Statuses.Valid
orderby token.Id
select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
if (tokens.Count == 0)
{
break;
}
@ -373,7 +716,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task SetApplicationIdAsync([NotNull] TToken token,
public virtual async Task SetApplicationIdAsync([NotNull] TToken token,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (token == null)
@ -419,7 +762,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task SetAuthorizationIdAsync([NotNull] TToken token,
public virtual async Task SetAuthorizationIdAsync([NotNull] TToken token,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (token == null)
@ -456,6 +799,200 @@ namespace OpenIddict.EntityFramework
}
}
/// <summary>
/// Sets the creation date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="date">The creation date.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetCreationDateAsync([NotNull] TToken token,
[CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.CreationDate = date;
return Task.CompletedTask;
}
/// <summary>
/// Sets the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="date">The expiration date.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetExpirationDateAsync([NotNull] TToken token,
[CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.ExpirationDate = date;
return Task.CompletedTask;
}
/// <summary>
/// Sets the payload associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="payload">The payload associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPayloadAsync([NotNull] TToken token, [CanBeNull] string payload, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.Payload = payload;
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="properties">The additional properties associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (properties == null)
{
token.Properties = null;
return Task.CompletedTask;
}
token.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the reference identifier associated with a token.
/// Note: depending on the manager used to create the token,
/// the reference identifier may be hashed for security reasons.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The reference identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetReferenceIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.ReferenceId = identifier;
return Task.CompletedTask;
}
/// <summary>
/// Sets the status associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="status">The status associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetStatusAsync([NotNull] TToken token, [CanBeNull] string status, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
token.Status = status;
return Task.CompletedTask;
}
/// <summary>
/// Sets the subject associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetSubjectAsync([NotNull] TToken token, [CanBeNull] string subject, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
token.Subject = subject;
return Task.CompletedTask;
}
/// <summary>
/// Sets the token type associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="type">The token type associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetTokenTypeAsync([NotNull] TToken token, [CanBeNull] string type, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The token type cannot be null or empty.", nameof(type));
}
token.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing token.
/// </summary>
@ -464,7 +1001,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -479,7 +1016,48 @@ namespace OpenIddict.EntityFramework
Context.Entry(token).State = EntityState.Modified;
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The token was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the token from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

15
src/OpenIddict.EntityFrameworkCore.Models/OpenIddict.EntityFrameworkCore.Models.csproj

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\packages.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<Description>Relational entities for the Entity Framework Core stores.</Description>
<Authors>Kévin Chalet</Authors>
<PackageTags>aspnetcore;authentication;jwt;openidconnect;openiddict;security</PackageTags>
</PropertyGroup>
</Project>

2
src/OpenIddict.Models/OpenIddictApplication.cs → src/OpenIddict.EntityFrameworkCore.Models/OpenIddictApplication.cs

@ -7,7 +7,7 @@
using System;
using System.Collections.Generic;
namespace OpenIddict.Models
namespace OpenIddict.EntityFrameworkCore.Models
{
/// <summary>
/// Represents an OpenIddict application.

5
src/OpenIddict.Models/OpenIddictAuthorization.cs → src/OpenIddict.EntityFrameworkCore.Models/OpenIddictAuthorization.cs

@ -7,7 +7,7 @@
using System;
using System.Collections.Generic;
namespace OpenIddict.Models
namespace OpenIddict.EntityFrameworkCore.Models
{
/// <summary>
/// Represents an OpenIddict authorization.
@ -72,8 +72,7 @@ namespace OpenIddict.Models
public virtual string Subject { get; set; }
/// <summary>
/// Gets or sets the list of tokens
/// associated with the current authorization.
/// Gets the list of tokens associated with the current authorization.
/// </summary>
public virtual IList<TToken> Tokens { get; } = new List<TToken>();

69
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictScope.cs

@ -0,0 +1,69 @@
/*
* 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;
namespace OpenIddict.EntityFrameworkCore.Models
{
/// <summary>
/// Represents an OpenIddict scope.
/// </summary>
public class OpenIddictScope : OpenIddictScope<string>
{
public OpenIddictScope()
{
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
}
}
/// <summary>
/// Represents an OpenIddict scope.
/// </summary>
public class OpenIddictScope<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// Gets or sets the concurrency token.
/// </summary>
public virtual string ConcurrencyToken { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Gets or sets the public description
/// associated with the current scope.
/// </summary>
public virtual string Description { get; set; }
/// <summary>
/// Gets or sets the display name
/// associated with the current scope.
/// </summary>
public virtual string DisplayName { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current scope.
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the unique name
/// associated with the current scope.
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// Gets or sets the additional properties serialized as a JSON object,
/// or <c>null</c> if no bag was associated with the current scope.
/// </summary>
public virtual string Properties { get; set; }
/// <summary>
/// Gets or sets the resources associated with the
/// current scope, serialized as a JSON array.
/// </summary>
public virtual string Resources { get; set; }
}
}

2
src/OpenIddict.Models/OpenIddictToken.cs → src/OpenIddict.EntityFrameworkCore.Models/OpenIddictToken.cs

@ -6,7 +6,7 @@
using System;
namespace OpenIddict.Models
namespace OpenIddict.EntityFrameworkCore.Models
{
/// <summary>
/// Represents an OpenIddict token.

2
src/OpenIddict.EntityFrameworkCore/OpenIddict.EntityFrameworkCore.csproj

@ -14,7 +14,7 @@
<ItemGroup>
<ProjectReference Include="..\OpenIddict.Core\OpenIddict.Core.csproj" />
<ProjectReference Include="..\OpenIddict.Stores\OpenIddict.Stores.csproj" />
<ProjectReference Include="..\OpenIddict.EntityFrameworkCore.Models\OpenIddict.EntityFrameworkCore.Models.csproj" />
</ItemGroup>
<ItemGroup>

123
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs

@ -0,0 +1,123 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.ComponentModel;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Core;
using OpenIddict.EntityFrameworkCore;
using OpenIddict.EntityFrameworkCore.Models;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Exposes the necessary methods required to configure the OpenIddict Entity Framework Core services.
/// </summary>
public class OpenIddictEntityFrameworkCoreBuilder
{
/// <summary>
/// Initializes a new instance of <see cref="OpenIddictEntityFrameworkCoreBuilder"/>.
/// </summary>
/// <param name="services">The services collection.</param>
public OpenIddictEntityFrameworkCoreBuilder([NotNull] IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
Services = services;
}
/// <summary>
/// Gets the services collection.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
/// <summary>
/// Amends the default OpenIddict Entity Framework Core configuration.
/// </summary>
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictEntityFrameworkCoreBuilder"/>.</returns>
public OpenIddictEntityFrameworkCoreBuilder Configure([NotNull] Action<OpenIddictEntityFrameworkCoreOptions> configuration)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
Services.Configure(configuration);
return this;
}
/// <summary>
/// Configures the OpenIddict Entity Framework Core stores to use the specified database context type.
/// </summary>
/// <typeparam name="TContext">The type of the <see cref="DbContext"/> used by OpenIddict.</typeparam>
/// <returns>The <see cref="OpenIddictEntityFrameworkCoreBuilder"/>.</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"/>.</returns>
public OpenIddictEntityFrameworkCoreBuilder UseDbContext([NotNull] Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
if (!typeof(DbContext).IsAssignableFrom(type))
{
throw new ArgumentException("The specified type is invalid.", nameof(type));
}
return Configure(options => options.ContextType = type);
}
/// <summary>
/// Configures OpenIddict to use the default OpenIddict
/// Entity Framework Core entities, with the specified key type.
/// </summary>
/// <returns>The <see cref="OpenIddictEntityFrameworkCoreBuilder"/>.</returns>
public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities<TKey>()
where TKey : IEquatable<TKey>
=> ReplaceDefaultEntities<OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>, TKey>();
/// <summary>
/// Configures OpenIddict to use the specified entities, derived
/// from the default OpenIddict Entity Framework Core entities.
/// </summary>
/// <returns>The <see cref="OpenIddictEntityFrameworkCoreBuilder"/>.</returns>
public OpenIddictEntityFrameworkCoreBuilder ReplaceDefaultEntities<TApplication, TAuthorization, TScope, TToken, TKey>()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TScope : OpenIddictScope<TKey>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TKey : IEquatable<TKey>
{
Services.Configure<OpenIddictCoreOptions>(options =>
{
options.DefaultApplicationType = typeof(TApplication);
options.DefaultAuthorizationType = typeof(TAuthorization);
options.DefaultScopeType = typeof(TScope);
options.DefaultTokenType = typeof(TToken);
});
return this;
}
}
}

6
src/OpenIddict.EntityFrameworkCore/OpenIddictCustomizer.cs → src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs

@ -9,7 +9,7 @@ using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Models;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore
{
@ -17,14 +17,14 @@ namespace OpenIddict.EntityFrameworkCore
/// Represents a model customizer able to register the entity sets
/// required by the OpenIddict stack in an Entity Framework context.
/// </summary>
public class OpenIddictCustomizer<TApplication, TAuthorization, TScope, TToken, TKey> : RelationalModelCustomizer
public class OpenIddictEntityFrameworkCoreCustomizer<TApplication, TAuthorization, TScope, TToken, TKey> : RelationalModelCustomizer
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TScope : OpenIddictScope<TKey>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TKey : IEquatable<TKey>
{
public OpenIddictCustomizer([NotNull] ModelCustomizerDependencies dependencies)
public OpenIddictEntityFrameworkCoreCustomizer([NotNull] ModelCustomizerDependencies dependencies)
: base(dependencies)
{
}

61
src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs → src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreExtensions.cs

@ -10,36 +10,69 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenIddict.EntityFrameworkCore;
using OpenIddict.Models;
using OpenIddict.EntityFrameworkCore.Models;
namespace Microsoft.Extensions.DependencyInjection
{
public static class OpenIddictExtensions
public static class OpenIddictEntityFrameworkCoreExtensions
{
/// <summary>
/// Registers the Entity Framework Core stores. Note: when using the Entity Framework Core stores,
/// the entities MUST be derived from the models contained in the OpenIddict.Models package.
/// Registers the Entity Framework Core stores services in the DI container and
/// configures OpenIddict to use the Entity Framework Core entities by default.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public static OpenIddictCoreBuilder AddEntityFrameworkCoreStores<TContext>([NotNull] this OpenIddictCoreBuilder builder)
where TContext : DbContext
/// <returns>The <see cref="OpenIddictEntityFrameworkCoreBuilder"/>.</returns>
public static OpenIddictEntityFrameworkCoreBuilder UseEntityFrameworkCore([NotNull] this OpenIddictCoreBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.SetDefaultApplicationEntity<OpenIddictApplication>()
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>()
.SetDefaultScopeEntity<OpenIddictScope>()
.SetDefaultTokenEntity<OpenIddictToken>();
builder.ReplaceApplicationStoreResolver<OpenIddictApplicationStoreResolver>()
.ReplaceAuthorizationStoreResolver<OpenIddictAuthorizationStoreResolver>()
.ReplaceScopeStoreResolver<OpenIddictScopeStoreResolver>()
.ReplaceTokenStoreResolver<OpenIddictTokenStoreResolver>();
builder.Services.TryAddScoped(typeof(OpenIddictApplicationStore<,,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationStore<,,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictScopeStore<,,>));
builder.Services.TryAddScoped(typeof(OpenIddictTokenStore<,,,,>));
return builder.ReplaceApplicationStoreResolver<OpenIddictApplicationStoreResolver<TContext>>()
.ReplaceAuthorizationStoreResolver<OpenIddictAuthorizationStoreResolver<TContext>>()
.ReplaceScopeStoreResolver<OpenIddictScopeStoreResolver<TContext>>()
.ReplaceTokenStoreResolver<OpenIddictTokenStoreResolver<TContext>>();
return new OpenIddictEntityFrameworkCoreBuilder(builder.Services);
}
/// <summary>
/// Registers the Entity Framework Core stores services in the DI container and
/// configures OpenIddict to use the Entity Framework Core entities by default.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="configuration">The configuration delegate used to configure the Entity Framework Core services.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public static OpenIddictCoreBuilder UseEntityFrameworkCore(
[NotNull] this OpenIddictCoreBuilder builder,
[NotNull] Action<OpenIddictEntityFrameworkCoreBuilder> configuration)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
configuration(builder.UseEntityFrameworkCore());
return builder;
}
/// <summary>
@ -85,7 +118,8 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(builder));
}
return builder.ReplaceService<IModelCustomizer, OpenIddictCustomizer<TApplication, TAuthorization, TScope, TToken, TKey>>();
return builder.ReplaceService<IModelCustomizer, OpenIddictEntityFrameworkCoreCustomizer<
TApplication, TAuthorization, TScope, TToken, TKey>>();
}
/// <summary>
@ -184,8 +218,7 @@ namespace Microsoft.Extensions.DependencyInjection
entity.HasMany(authorization => authorization.Tokens)
.WithOne(token => token.Authorization)
.HasForeignKey("AuthorizationId")
.IsRequired(required: false)
.OnDelete(DeleteBehavior.Cascade);
.IsRequired(required: false);
entity.ToTable("OpenIddictAuthorizations");
});

25
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreOptions.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;
using Microsoft.EntityFrameworkCore;
namespace OpenIddict.EntityFrameworkCore
{
/// <summary>
/// Provides various settings needed to configure
/// the OpenIddict Entity Framework Core integration.
/// </summary>
public 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 ContextType { get; set; }
}
}

39
src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictApplicationStoreResolver.cs

@ -1,26 +1,35 @@
using System;
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Concurrent;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore
{
/// <summary>
/// Exposes a method allowing to resolve an application store.
/// </summary>
public class OpenIddictApplicationStoreResolver<TContext> : IOpenIddictApplicationStoreResolver
where TContext : DbContext
public class OpenIddictApplicationStoreResolver : IOpenIddictApplicationStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictApplicationStoreResolver([NotNull] IServiceProvider provider)
public OpenIddictApplicationStoreResolver(
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options,
[NotNull] IServiceProvider provider)
{
_options = options;
_provider = provider;
}
@ -45,9 +54,19 @@ namespace OpenIddict.EntityFrameworkCore
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified application type is not compatible with the Entity Framework Core stores.")
.Append("When enabling the Entity Framework Core stores, make sure you use the built-in generic ")
.Append("'OpenIddictApplication' entity (from the 'OpenIddict.Models' package) or a custom entity ")
.Append("that inherits from the generic 'OpenIddictApplication' entity.")
.Append("When enabling the Entity Framework Core stores, make sure you use the built-in ")
.Append("'OpenIddictApplication' entity (from the 'OpenIddict.EntityFrameworkCore.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictApplication' entity.")
.ToString());
}
var context = _options.CurrentValue.ContextType;
if (context == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("No Entity Framework Core context was specified in the OpenIddict options.")
.Append("To configure the OpenIddict Entity Framework Core stores to use a specific 'DbContext', ")
.Append("use 'options.AddEntityFrameworkCoreStores().UseContext<TContext>()'.")
.ToString());
}
@ -55,7 +74,7 @@ namespace OpenIddict.EntityFrameworkCore
/* TApplication: */ key,
/* TAuthorization: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TContext: */ typeof(TContext),
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});

38
src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictAuthorizationStoreResolver.cs

@ -1,26 +1,36 @@
using System;
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Concurrent;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore
{
/// <summary>
/// Exposes a method allowing to resolve an authorization store.
/// </summary>
public class OpenIddictAuthorizationStoreResolver<TContext> : IOpenIddictAuthorizationStoreResolver
where TContext : DbContext
public class OpenIddictAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictAuthorizationStoreResolver([NotNull] IServiceProvider provider)
public OpenIddictAuthorizationStoreResolver(
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options,
[NotNull] IServiceProvider provider)
{
_options = options;
_provider = provider;
}
@ -45,9 +55,19 @@ namespace OpenIddict.EntityFrameworkCore
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified authorization type is not compatible with the Entity Framework Core stores.")
.Append("When enabling the Entity Framework Core stores, make sure you use the built-in generic ")
.Append("'OpenIddictAuthorization' entity (from the 'OpenIddict.Models' package) or a custom entity ")
.Append("that inherits from the generic 'OpenIddictAuthorization' entity.")
.Append("When enabling the Entity Framework Core stores, make sure you use the built-in ")
.Append("'OpenIddictAuthorization' entity (from the 'OpenIddict.EntityFrameworkCore.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictAuthorization' entity.")
.ToString());
}
var context = _options.CurrentValue.ContextType;
if (context == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("No Entity Framework Core context was specified in the OpenIddict options.")
.Append("To configure the OpenIddict Entity Framework Core stores to use a specific 'DbContext', ")
.Append("use 'options.AddEntityFrameworkCoreStores().UseContext<TContext>()'.")
.ToString());
}
@ -55,7 +75,7 @@ namespace OpenIddict.EntityFrameworkCore
/* TAuthorization: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TContext: */ typeof(TContext),
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});

39
src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictScopeStoreResolver.cs

@ -1,26 +1,35 @@
using System;
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Concurrent;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore
{
/// <summary>
/// Exposes a method allowing to resolve a scope store.
/// </summary>
public class OpenIddictScopeStoreResolver<TContext> : IOpenIddictScopeStoreResolver
where TContext : DbContext
public class OpenIddictScopeStoreResolver : IOpenIddictScopeStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictScopeStoreResolver([NotNull] IServiceProvider provider)
public OpenIddictScopeStoreResolver(
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options,
[NotNull] IServiceProvider provider)
{
_options = options;
_provider = provider;
}
@ -45,15 +54,25 @@ namespace OpenIddict.EntityFrameworkCore
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified scope type is not compatible with the Entity Framework Core stores.")
.Append("When enabling the Entity Framework Core stores, make sure you use the built-in generic ")
.Append("'OpenIdScope' entity (from the 'OpenIddict.Models' package) or a custom entity ")
.Append("that inherits from the generic 'OpenIddictScope' entity.")
.Append("When enabling the Entity Framework Core stores, make sure you use the built-in ")
.Append("'OpenIdScope' entity (from the 'OpenIddict.EntityFrameworkCore.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictScope' entity.")
.ToString());
}
var context = _options.CurrentValue.ContextType;
if (context == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("No Entity Framework Core context was specified in the OpenIddict options.")
.Append("To configure the OpenIddict Entity Framework Core stores to use a specific 'DbContext', ")
.Append("use 'options.AddEntityFrameworkCoreStores().UseContext<TContext>()'.")
.ToString());
}
return typeof(OpenIddictScopeStore<,,>).MakeGenericType(
/* TScope: */ key,
/* TContext: */ typeof(TContext),
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});

39
src/OpenIddict.EntityFrameworkCore/Resolvers/OpenIddictTokenStoreResolver.cs

@ -1,26 +1,35 @@
using System;
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Concurrent;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore
{
/// <summary>
/// Exposes a method allowing to resolve a token store.
/// </summary>
public class OpenIddictTokenStoreResolver<TContext> : IOpenIddictTokenStoreResolver
where TContext : DbContext
public class OpenIddictTokenStoreResolver : IOpenIddictTokenStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> _options;
private readonly IServiceProvider _provider;
public OpenIddictTokenStoreResolver([NotNull] IServiceProvider provider)
public OpenIddictTokenStoreResolver(
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options,
[NotNull] IServiceProvider provider)
{
_options = options;
_provider = provider;
}
@ -45,9 +54,19 @@ namespace OpenIddict.EntityFrameworkCore
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified token type is not compatible with the Entity Framework Core stores.")
.Append("When enabling the Entity Framework Core stores, make sure you use the built-in generic ")
.Append("'OpenIddictToken' entity (from the 'OpenIddict.Models' package) or a custom entity ")
.Append("that inherits from the generic 'OpenIddictToken' entity.")
.Append("When enabling the Entity Framework Core stores, make sure you use the built-in ")
.Append("'OpenIddictToken' entity (from the 'OpenIddict.EntityFrameworkCore.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictToken' entity.")
.ToString());
}
var context = _options.CurrentValue.ContextType;
if (context == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("No Entity Framework Core context was specified in the OpenIddict options.")
.Append("To configure the OpenIddict Entity Framework Core stores to use a specific 'DbContext', ")
.Append("use 'options.AddEntityFrameworkCoreStores().UseContext<TContext>()'.")
.ToString());
}
@ -55,7 +74,7 @@ namespace OpenIddict.EntityFrameworkCore
/* TToken: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TAuthorization: */ root.GenericTypeArguments[2],
/* TContext: */ typeof(TContext),
/* TContext: */ context,
/* TKey: */ root.GenericTypeArguments[0]);
});

649
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs

@ -7,8 +7,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -16,14 +18,15 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory;
using OpenIddict.Models;
using OpenIddict.Stores;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore
{
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictApplicationStore<TContext> : OpenIddictApplicationStore<OpenIddictApplication,
@ -31,15 +34,14 @@ namespace OpenIddict.EntityFrameworkCore
OpenIddictToken, TContext, string>
where TContext : DbContext
{
public OpenIddictApplicationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
@ -49,59 +51,68 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <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 OpenIddictApplicationStore<TApplication, TAuthorization, TToken, TContext, TKey> :
OpenIddictApplicationStore<TApplication, TAuthorization, TToken, TKey>
public class OpenIddictApplicationStore<TApplication, TAuthorization, TToken, TContext, TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(cache)
public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Cache = cache;
Context = context;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected virtual TContext Context { get; }
protected TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
protected DbSet<TApplication> Applications => Context.Set<TApplication>();
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
protected DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
protected DbSet<TToken> Tokens => Context.Set<TToken>();
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <summary>
/// Determines the number of applications that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
=> Applications.LongCountAsync();
/// <summary>
/// Determines the number of applications that match the specified query.
@ -113,7 +124,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications that match the specified query.
/// </returns>
public override Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TApplication>, IQueryable<TResult>> query, CancellationToken cancellationToken)
public virtual Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TApplication>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
@ -131,7 +142,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -151,7 +162,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -229,8 +240,19 @@ namespace OpenIddict.EntityFrameworkCore
Context.Remove(application);
await Context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
try
{
await Context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The application was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the application from the database and retry the operation.")
.ToString(), exception);
}
}
}
@ -243,7 +265,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public override Task<TApplication> FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
public virtual Task<TApplication> FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
@ -272,7 +294,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public override Task<TApplication> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
public virtual Task<TApplication> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
@ -301,7 +323,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
/// </returns>
public override async Task<ImmutableArray<TApplication>> FindByPostLogoutRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
public virtual async Task<ImmutableArray<TApplication>> FindByPostLogoutRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(address))
{
@ -354,7 +376,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
/// </returns>
public override async Task<ImmutableArray<TApplication>> FindByRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
public virtual async Task<ImmutableArray<TApplication>> FindByRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(address))
{
@ -410,7 +432,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public override Task<TResult> GetAsync<TState, TResult>(
public virtual Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -422,6 +444,296 @@ namespace OpenIddict.EntityFrameworkCore
return query(Applications.AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client identifier associated with the application.
/// </returns>
public virtual ValueTask<string> GetClientIdAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ClientId);
}
/// <summary>
/// Retrieves the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client secret associated with the application.
/// </returns>
public virtual ValueTask<string> GetClientSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ClientSecret);
}
/// <summary>
/// Retrieves the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// </returns>
public virtual ValueTask<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.Type);
}
/// <summary>
/// Retrieves the consent type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the consent type of the application (by default, "explicit").
/// </returns>
public virtual ValueTask<string> GetConsentTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ConsentType);
}
/// <summary>
/// Retrieves the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// </returns>
public virtual ValueTask<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.DisplayName);
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(ConvertIdentifierToString(application.Id));
}
/// <summary>
/// Retrieves the permissions associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the permissions associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetPermissionsAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Permissions))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified permissions is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions);
var permissions = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Permissions)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(permissions);
}
/// <summary>
/// Retrieves the logout callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the post_logout_redirect_uri associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetPostLogoutRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.PostLogoutRedirectUris))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.PostLogoutRedirectUris)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(addresses);
}
/// <summary>
/// Retrieves the additional properties associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the application.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(application.Properties));
}
/// <summary>
/// Retrieves the callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the redirect_uri associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.RedirectUris))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.RedirectUris)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(addresses);
}
/// <summary>
/// Instantiates a new application.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated application, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TApplication> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TApplication>(new TApplication());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TApplication>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Applications.OrderBy(application => application.Id).AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
@ -434,7 +746,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public override async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -446,6 +758,232 @@ namespace OpenIddict.EntityFrameworkCore
return ImmutableArray.CreateRange(await query(Applications.AsTracking(), state).ToListAsync(cancellationToken));
}
/// <summary>
/// Sets the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientIdAsync([NotNull] TApplication application,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ClientId = identifier;
return Task.CompletedTask;
}
/// <summary>
/// Sets the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientSecretAsync([NotNull] TApplication application,
[CanBeNull] string secret, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ClientSecret = secret;
return Task.CompletedTask;
}
/// <summary>
/// Sets the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientTypeAsync([NotNull] TApplication application,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Sets the consent type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The consent type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetConsentTypeAsync([NotNull] TApplication application,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ConsentType = type;
return Task.CompletedTask;
}
/// <summary>
/// Sets the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="name">The display name associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDisplayNameAsync([NotNull] TApplication application,
[CanBeNull] string name, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.DisplayName = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the permissions associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="permissions">The permissions associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPermissionsAsync([NotNull] TApplication application, ImmutableArray<string> permissions, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (permissions.IsDefaultOrEmpty)
{
application.Permissions = null;
return Task.CompletedTask;
}
application.Permissions = new JArray(permissions.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the logout callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="addresses">The logout callback addresses associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPostLogoutRedirectUrisAsync([NotNull] TApplication application,
ImmutableArray<string> addresses, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (addresses.IsDefaultOrEmpty)
{
application.PostLogoutRedirectUris = null;
return Task.CompletedTask;
}
application.PostLogoutRedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="properties">The additional properties associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (properties == null)
{
application.Properties = null;
return Task.CompletedTask;
}
application.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="addresses">The callback addresses associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetRedirectUrisAsync([NotNull] TApplication application,
ImmutableArray<string> addresses, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (addresses.IsDefaultOrEmpty)
{
application.RedirectUris = null;
return Task.CompletedTask;
}
application.RedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing application.
/// </summary>
@ -454,7 +992,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -469,7 +1007,48 @@ namespace OpenIddict.EntityFrameworkCore
Context.Update(application);
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The application was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the application from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

447
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs

@ -7,8 +7,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -16,15 +18,15 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
using OpenIddict.Stores;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore
{
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictAuthorizationStore<TContext> : OpenIddictAuthorizationStore<OpenIddictAuthorization,
@ -32,15 +34,14 @@ namespace OpenIddict.EntityFrameworkCore
OpenIddictToken, TContext, string>
where TContext : DbContext
{
public OpenIddictAuthorizationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
@ -50,59 +51,68 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <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 OpenIddictAuthorizationStore<TAuthorization, TApplication, TToken, TContext, TKey> :
OpenIddictAuthorizationStore<TAuthorization, TApplication, TToken, TKey>
public class OpenIddictAuthorizationStore<TAuthorization, TApplication, TToken, TContext, TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(cache)
public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Cache = cache;
Context = context;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected virtual TContext Context { get; }
protected TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
protected DbSet<TApplication> Applications => Context.Set<TApplication>();
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
protected DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
protected DbSet<TToken> Tokens => Context.Set<TToken>();
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <summary>
/// Determines the number of authorizations that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
=> Authorizations.LongCountAsync();
/// <summary>
/// Determines the number of authorizations that match the specified query.
@ -114,7 +124,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations that match the specified query.
/// </returns>
public override Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TAuthorization>, IQueryable<TResult>> query, CancellationToken cancellationToken)
public virtual Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TAuthorization>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
@ -132,7 +142,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -152,7 +162,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -206,8 +216,19 @@ namespace OpenIddict.EntityFrameworkCore
Context.Remove(authorization);
await Context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
try
{
await Context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The authorization was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the authorization from the database and retry the operation.")
.ToString(), exception);
}
}
}
@ -222,7 +243,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the subject/client.
/// </returns>
public override async Task<ImmutableArray<TAuthorization>> FindAsync(
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
@ -268,7 +289,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public override async Task<ImmutableArray<TAuthorization>> FindAsync(
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken)
{
@ -321,7 +342,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public override async Task<ImmutableArray<TAuthorization>> FindAsync(
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken)
{
@ -378,7 +399,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier.
/// </returns>
public override Task<TAuthorization> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
public virtual Task<TAuthorization> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
@ -409,7 +430,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the specified subject.
/// </returns>
public override async Task<ImmutableArray<TAuthorization>> FindBySubjectAsync(
public virtual async Task<ImmutableArray<TAuthorization>> FindBySubjectAsync(
[NotNull] string subject, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
@ -441,7 +462,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the authorization.
/// </returns>
public override async ValueTask<string> GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual async ValueTask<string> GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -480,7 +501,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public override Task<TResult> GetAsync<TState, TResult>(
public virtual Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TAuthorization>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -494,6 +515,171 @@ namespace OpenIddict.EntityFrameworkCore
.AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the authorization.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(ConvertIdentifierToString(authorization.Id));
}
/// <summary>
/// Retrieves the additional properties associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the authorization.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (string.IsNullOrEmpty(authorization.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(authorization.Properties));
}
/// <summary>
/// Retrieves the scopes associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes associated with the specified authorization.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetScopesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (string.IsNullOrEmpty(authorization.Scopes))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
return new ValueTask<ImmutableArray<string>>(JArray.Parse(authorization.Scopes).Select(element => (string) element).ToImmutableArray());
}
/// <summary>
/// Retrieves the status associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetStatusAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Status);
}
/// <summary>
/// Retrieves the subject associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetSubjectAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Subject);
}
/// <summary>
/// Retrieves the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the type associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetTypeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Type);
}
/// <summary>
/// Instantiates a new authorization.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated authorization, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TAuthorization> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TAuthorization>(new TAuthorization());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Authorizations.Include(authorization => authorization.Application)
.OrderBy(authorization => authorization.Id)
.AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
@ -506,7 +692,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public override async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TAuthorization>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -527,7 +713,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task PruneAsync(CancellationToken cancellationToken)
public virtual async Task PruneAsync(CancellationToken cancellationToken)
{
// Note: Entity Framework Core doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
@ -535,14 +721,6 @@ namespace OpenIddict.EntityFrameworkCore
IList<Exception> exceptions = null;
IQueryable<TAuthorization> Query(IQueryable<TAuthorization> authorizations, int offset)
=> (from authorization in authorizations.Include(authorization => authorization.Tokens).AsTracking()
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
!authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid))
orderby authorization.Id
select authorization).Skip(offset).Take(1_000);
async Task<IDbContextTransaction> CreateTransactionAsync()
{
// Note: transactions that specify an explicit isolation level are only supported by
@ -579,8 +757,15 @@ namespace OpenIddict.EntityFrameworkCore
// and thus prevent them from being concurrently modified outside this block.
using (var transaction = await CreateTransactionAsync())
{
var authorizations = await ListAsync((source, state) => Query(source, state), offset, cancellationToken);
if (authorizations.IsEmpty)
var authorizations =
await (from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking()
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
!authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid))
orderby authorization.Id
select authorization).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
if (authorizations.Count == 0)
{
break;
}
@ -625,7 +810,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task SetApplicationIdAsync([NotNull] TAuthorization authorization,
public virtual async Task SetApplicationIdAsync([NotNull] TAuthorization authorization,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (authorization == null)
@ -662,6 +847,129 @@ namespace OpenIddict.EntityFrameworkCore
}
}
/// <summary>
/// Sets the additional properties associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="properties">The additional properties associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (properties == null)
{
authorization.Properties = null;
return Task.CompletedTask;
}
authorization.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the scopes associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="scopes">The scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetScopesAsync([NotNull] TAuthorization authorization,
ImmutableArray<string> scopes, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (scopes.IsDefaultOrEmpty)
{
authorization.Scopes = null;
return Task.CompletedTask;
}
authorization.Scopes = new JArray(scopes.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the status associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="status">The status associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetStatusAsync([NotNull] TAuthorization authorization,
[CanBeNull] string status, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Status = status;
return Task.CompletedTask;
}
/// <summary>
/// Sets the subject associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetSubjectAsync([NotNull] TAuthorization authorization,
[CanBeNull] string subject, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Subject = subject;
return Task.CompletedTask;
}
/// <summary>
/// Sets the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="type">The type associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetTypeAsync([NotNull] TAuthorization authorization,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing authorization.
/// </summary>
@ -670,7 +978,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -685,7 +993,48 @@ namespace OpenIddict.EntityFrameworkCore
Context.Update(authorization);
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The authorization was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the authorization from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

420
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs

@ -6,33 +6,36 @@
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using OpenIddict.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore
{
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictScopeStore<TContext> : OpenIddictScopeStore<OpenIddictScope, TContext, string>
where TContext : DbContext
{
public OpenIddictScopeStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
@ -40,44 +43,54 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictScopeStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </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 OpenIddictScopeStore<TScope, TContext, TKey> : Stores.OpenIddictScopeStore<TScope, TKey>
public class OpenIddictScopeStore<TScope, TContext, TKey> : IOpenIddictScopeStore<TScope>
where TScope : OpenIddictScope<TKey>, new()
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictScopeStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(cache)
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Cache = cache;
Context = context;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected virtual TContext Context { get; }
protected TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TScope"/> entity.
/// </summary>
protected DbSet<TScope> Scopes => Context.Set<TScope>();
private DbSet<TScope> Scopes => Context.Set<TScope>();
/// <summary>
/// Determines the number of scopes that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
=> Scopes.LongCountAsync();
/// <summary>
/// Determines the number of scopes that match the specified query.
@ -89,7 +102,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes that match the specified query.
/// </returns>
public override Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken)
public virtual Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
@ -107,7 +120,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual Task CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -127,7 +140,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual async Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -136,7 +149,18 @@ namespace OpenIddict.EntityFrameworkCore
Context.Remove(scope);
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The scope was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the scope from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
@ -148,7 +172,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the identifier.
/// </returns>
public override Task<TScope> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
public virtual Task<TScope> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
@ -177,7 +201,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the specified name.
/// </returns>
public override Task<TScope> FindByNameAsync([NotNull] string name, CancellationToken cancellationToken)
public virtual Task<TScope> FindByNameAsync([NotNull] string name, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(name))
{
@ -206,7 +230,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes corresponding to the specified names.
/// </returns>
public override async Task<ImmutableArray<TScope>> FindByNamesAsync(
public virtual async Task<ImmutableArray<TScope>> FindByNamesAsync(
ImmutableArray<string> names, CancellationToken cancellationToken)
{
if (names.Any(name => string.IsNullOrEmpty(name)))
@ -236,7 +260,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes associated with the specified resource.
/// </returns>
public override async Task<ImmutableArray<TScope>> FindByResourceAsync(
public virtual async Task<ImmutableArray<TScope>> FindByResourceAsync(
[NotNull] string resource, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(resource))
@ -285,7 +309,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public override Task<TResult> GetAsync<TState, TResult>(
public virtual Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -297,6 +321,182 @@ namespace OpenIddict.EntityFrameworkCore
return query(Scopes.AsTracking(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the description associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the description associated with the specified scope.
/// </returns>
public virtual ValueTask<string> GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.Description);
}
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the scope.
/// </returns>
public virtual ValueTask<string> GetDisplayNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.DisplayName);
}
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the scope.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(ConvertIdentifierToString(scope.Id));
}
/// <summary>
/// Retrieves the name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the name associated with the specified scope.
/// </returns>
public virtual ValueTask<string> GetNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.Name);
}
/// <summary>
/// Retrieves the additional properties associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the scope.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(scope.Properties));
}
/// <summary>
/// Retrieves the resources associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the resources associated with the scope.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetResourcesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Resources))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified resources is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources);
var resources = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(scope.Resources)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(resources);
}
/// <summary>
/// Instantiates a new scope.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated scope, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TScope> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TScope>(new TScope());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TScope>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Scopes.OrderBy(scope => scope.Id).AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
@ -309,7 +509,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public override async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -321,6 +521,125 @@ namespace OpenIddict.EntityFrameworkCore
return ImmutableArray.CreateRange(await query(Scopes.AsTracking(), state).ToListAsync(cancellationToken));
}
/// <summary>
/// Sets the description associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="description">The description associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.Description = description;
return Task.CompletedTask;
}
/// <summary>
/// Sets the display name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="name">The display name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDisplayNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.DisplayName = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="name">The name associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.Name = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="properties">The additional properties associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (properties == null)
{
scope.Properties = null;
return Task.CompletedTask;
}
scope.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the resources associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="resources">The resources associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetResourcesAsync([NotNull] TScope scope, ImmutableArray<string> resources, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (resources.IsDefaultOrEmpty)
{
scope.Resources = null;
return Task.CompletedTask;
}
scope.Resources = new JArray(resources.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing scope.
/// </summary>
@ -329,7 +648,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -344,7 +663,48 @@ namespace OpenIddict.EntityFrameworkCore
Context.Update(scope);
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The scope was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the scope from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

578
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs

@ -7,8 +7,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@ -16,16 +18,15 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.Stores;
using OpenIddict.EntityFrameworkCore.Models;
namespace OpenIddict.EntityFrameworkCore
{
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
public class OpenIddictTokenStore<TContext> : OpenIddictTokenStore<OpenIddictToken,
@ -33,15 +34,14 @@ namespace OpenIddict.EntityFrameworkCore
OpenIddictAuthorization, TContext, string>
where TContext : DbContext
{
public OpenIddictTokenStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TContext">The type of the Entity Framework database context.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
@ -51,59 +51,68 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictTokenStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(context, cache)
public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
{
}
}
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// Note: this class can only be used with the default OpenIddict entities.
/// </summary>
/// <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 OpenIddictTokenStore<TToken, TApplication, TAuthorization, TContext, TKey> :
OpenIddictTokenStore<TToken, TApplication, TAuthorization, TKey>
public class OpenIddictTokenStore<TToken, TApplication, TAuthorization, TContext, TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictTokenStore([NotNull] TContext context, [NotNull] IMemoryCache cache)
: base(cache)
public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
Cache = cache;
Context = context;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected virtual TContext Context { get; }
protected TContext Context { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
protected DbSet<TApplication> Applications => Context.Set<TApplication>();
private DbSet<TApplication> Applications => Context.Set<TApplication>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TAuthorization"/> entity.
/// </summary>
protected DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
private DbSet<TAuthorization> Authorizations => Context.Set<TAuthorization>();
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TToken"/> entity.
/// </summary>
protected DbSet<TToken> Tokens => Context.Set<TToken>();
private DbSet<TToken> Tokens => Context.Set<TToken>();
/// <summary>
/// Determines the number of tokens that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
=> Tokens.LongCountAsync();
/// <summary>
/// Determines the number of tokens that match the specified query.
@ -115,7 +124,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens that match the specified query.
/// </returns>
public override Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TToken>, IQueryable<TResult>> query, CancellationToken cancellationToken)
public virtual Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TToken>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
@ -133,7 +142,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task CreateAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual Task CreateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -150,8 +159,10 @@ namespace OpenIddict.EntityFrameworkCore
/// </summary>
/// <param name="token">The token to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public override Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -160,7 +171,18 @@ namespace OpenIddict.EntityFrameworkCore
Context.Remove(token);
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The token was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the token from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
@ -172,7 +194,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified application.
/// </returns>
public override async Task<ImmutableArray<TToken>> FindByApplicationIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
public virtual async Task<ImmutableArray<TToken>> FindByApplicationIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
@ -210,7 +232,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified authorization.
/// </returns>
public override async Task<ImmutableArray<TToken>> FindByAuthorizationIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
public virtual async Task<ImmutableArray<TToken>> FindByAuthorizationIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
@ -248,7 +270,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier.
/// </returns>
public override Task<TToken> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
public virtual Task<TToken> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
@ -281,7 +303,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified reference identifier.
/// </returns>
public override Task<TToken> FindByReferenceIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
public virtual Task<TToken> FindByReferenceIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
@ -313,7 +335,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified subject.
/// </returns>
public override async Task<ImmutableArray<TToken>> FindBySubjectAsync([NotNull] string subject, CancellationToken cancellationToken)
public virtual async Task<ImmutableArray<TToken>> FindBySubjectAsync([NotNull] string subject, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
@ -345,7 +367,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the token.
/// </returns>
public override async ValueTask<string> GetApplicationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual async ValueTask<string> GetApplicationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -384,7 +406,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public override Task<TResult> GetAsync<TState, TResult>(
public virtual Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TToken>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -408,7 +430,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization identifier associated with the token.
/// </returns>
public override async ValueTask<string> GetAuthorizationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual async ValueTask<string> GetAuthorizationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -435,6 +457,226 @@ namespace OpenIddict.EntityFrameworkCore
return ConvertIdentifierToString(token.Authorization.Id);
}
/// <summary>
/// Retrieves the creation date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified token.
/// </returns>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<DateTimeOffset?>(token.CreationDate);
}
/// <summary>
/// Retrieves the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the expiration date associated with the specified token.
/// </returns>
public virtual ValueTask<DateTimeOffset?> GetExpirationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<DateTimeOffset?>(token.ExpirationDate);
}
/// <summary>
/// Retrieves the unique identifier associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(ConvertIdentifierToString(token.Id));
}
/// <summary>
/// Retrieves the payload associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the payload associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetPayloadAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Payload);
}
/// <summary>
/// Retrieves the additional properties associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the token.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(token.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(token.Properties));
}
/// <summary>
/// Retrieves the reference identifier associated with a token.
/// Note: depending on the manager used to create the token,
/// the reference identifier may be hashed for security reasons.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the reference identifier associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetReferenceIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.ReferenceId);
}
/// <summary>
/// Retrieves the status associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetStatusAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Status);
}
/// <summary>
/// Retrieves the subject associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetSubjectAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Subject);
}
/// <summary>
/// Retrieves the token type associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token type associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetTokenTypeAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Type);
}
/// <summary>
/// Instantiates a new token.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated token, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TToken> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TToken>(new TToken());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TToken>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var query = Tokens.Include(token => token.Application)
.Include(token => token.Authorization)
.OrderBy(token => token.Id)
.AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
@ -447,7 +689,7 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public override async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TToken>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
@ -469,7 +711,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task PruneAsync(CancellationToken cancellationToken)
public virtual async Task PruneAsync(CancellationToken cancellationToken)
{
// Note: Entity Framework Core doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
@ -477,13 +719,6 @@ namespace OpenIddict.EntityFrameworkCore
IList<Exception> exceptions = null;
IQueryable<TToken> Query(IQueryable<TToken> tokens, int offset)
=> (from token in tokens.AsTracking()
where token.ExpirationDate < DateTimeOffset.UtcNow ||
token.Status != OpenIddictConstants.Statuses.Valid
orderby token.Id
select token).Skip(offset).Take(1_000);
async Task<IDbContextTransaction> CreateTransactionAsync()
{
// Note: transactions that specify an explicit isolation level are only supported by
@ -520,8 +755,14 @@ namespace OpenIddict.EntityFrameworkCore
// and thus prevent them from being concurrently modified outside this block.
using (var transaction = await CreateTransactionAsync())
{
var tokens = await ListAsync((source, state) => Query(source, state), offset, cancellationToken);
if (tokens.IsEmpty)
var tokens =
await (from token in Tokens.AsTracking()
where token.ExpirationDate < DateTimeOffset.UtcNow ||
token.Status != OpenIddictConstants.Statuses.Valid
orderby token.Id
select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
if (tokens.Count == 0)
{
break;
}
@ -561,7 +802,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task SetApplicationIdAsync([NotNull] TToken token,
public virtual async Task SetApplicationIdAsync([NotNull] TToken token,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (token == null)
@ -607,7 +848,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task SetAuthorizationIdAsync([NotNull] TToken token,
public virtual async Task SetAuthorizationIdAsync([NotNull] TToken token,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (token == null)
@ -644,6 +885,200 @@ namespace OpenIddict.EntityFrameworkCore
}
}
/// <summary>
/// Sets the creation date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="date">The creation date.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetCreationDateAsync([NotNull] TToken token,
[CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.CreationDate = date;
return Task.CompletedTask;
}
/// <summary>
/// Sets the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="date">The expiration date.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetExpirationDateAsync([NotNull] TToken token,
[CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.ExpirationDate = date;
return Task.CompletedTask;
}
/// <summary>
/// Sets the payload associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="payload">The payload associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPayloadAsync([NotNull] TToken token, [CanBeNull] string payload, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.Payload = payload;
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="properties">The additional properties associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (properties == null)
{
token.Properties = null;
return Task.CompletedTask;
}
token.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the reference identifier associated with a token.
/// Note: depending on the manager used to create the token,
/// the reference identifier may be hashed for security reasons.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The reference identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetReferenceIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.ReferenceId = identifier;
return Task.CompletedTask;
}
/// <summary>
/// Sets the status associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="status">The status associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetStatusAsync([NotNull] TToken token, [CanBeNull] string status, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
token.Status = status;
return Task.CompletedTask;
}
/// <summary>
/// Sets the subject associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetSubjectAsync([NotNull] TToken token, [CanBeNull] string subject, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
token.Subject = subject;
return Task.CompletedTask;
}
/// <summary>
/// Sets the token type associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="type">The token type associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetTokenTypeAsync([NotNull] TToken token, [CanBeNull] string type, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The token type cannot be null or empty.", nameof(type));
}
token.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing token.
/// </summary>
@ -652,7 +1087,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -667,7 +1102,48 @@ namespace OpenIddict.EntityFrameworkCore
Context.Update(token);
return Context.SaveChangesAsync(cancellationToken);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException exception)
{
throw new OpenIddictException(OpenIddictConstants.Exceptions.ConcurrencyError, new StringBuilder()
.AppendLine("The token was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the token from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

44
src/OpenIddict.Server/Internal/OpenIddictServerProvider.Helpers.cs

@ -391,13 +391,21 @@ namespace OpenIddict.Server
return true;
}
catch (Exception exception)
catch (OpenIddictException exception) when (exception.Reason == OpenIddictConstants.Exceptions.ConcurrencyError)
{
_logger.LogDebug(exception, "An exception occurred while trying to revoke the authorization " +
_logger.LogDebug(exception, "A concurrency exception occurred while trying to revoke the authorization " +
"associated with the token '{Identifier}'.", identifier);
return false;
}
catch (Exception exception)
{
_logger.LogWarning(exception, "An exception occurred while trying to revoke the authorization " +
"associated with the token '{Identifier}'.", identifier);
return false;
}
}
private async Task<bool> TryRevokeTokenAsync([NotNull] object token)
@ -416,10 +424,16 @@ namespace OpenIddict.Server
return true;
}
catch (OpenIddictException exception) when (exception.Reason == OpenIddictConstants.Exceptions.ConcurrencyError)
{
_logger.LogDebug(exception, "A concurrency exception occurred while trying to revoke the token '{Identifier}'.", identifier);
return false;
}
catch (Exception exception)
{
_logger.LogDebug(exception, "An exception occurred while trying to revoke " +
"the token '{Identifier}'.", identifier);
_logger.LogWarning(exception, "An exception occurred while trying to revoke the token '{Identifier}'.", identifier);
return false;
}
@ -466,10 +480,16 @@ namespace OpenIddict.Server
return true;
}
catch (OpenIddictException exception) when (exception.Reason == OpenIddictConstants.Exceptions.ConcurrencyError)
{
_logger.LogDebug(exception, "A concurrency exception occurred while trying to redeem with the token '{Identifier}'.", identifier);
return false;
}
catch (Exception exception)
{
_logger.LogDebug(exception, "An exception occurred while trying to " +
"redeem the token '{Identifier}'.", identifier);
_logger.LogWarning(exception, "An exception occurred while trying to redeem the token '{Identifier}'.", identifier);
return false;
}
@ -497,13 +517,21 @@ namespace OpenIddict.Server
return true;
}
catch (Exception exception)
catch (OpenIddictException exception) when (exception.Reason == OpenIddictConstants.Exceptions.ConcurrencyError)
{
_logger.LogDebug(exception, "An exception occurred while trying to update the " +
_logger.LogDebug(exception, "A concurrency exception occurred while trying to update the " +
"expiration date of the token '{Identifier}'.", identifier);
return false;
}
catch (Exception exception)
{
_logger.LogWarning(exception, "An exception occurred while trying to update the " +
"expiration date of the token '{Identifier}'.", identifier);
return false;
}
}
private IEnumerable<(string property, string parameter, OpenIdConnectParameter value)> GetParameters(

10
src/OpenIddict.Server/OpenIddictServerOptions.cs

@ -59,11 +59,6 @@ namespace OpenIddict.Server
OpenIdConnectConstants.Claims.Subject
};
/// <summary>
/// Gets or sets a boolean indicating whether scope validation is enabled.
/// </summary>
public bool EnableScopeValidation { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether token revocation should be disabled.
/// When disabled, authorization code and refresh tokens are not stored
@ -80,6 +75,11 @@ namespace OpenIddict.Server
/// </summary>
public bool EnableRequestCaching { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether scope validation is enabled.
/// </summary>
public bool EnableScopeValidation { get; set; }
/// <summary>
/// Gets the OAuth2/OpenID Connect flows enabled for this application.
/// </summary>

25
src/OpenIddict.Stores/OpenIddict.Stores.csproj

@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\packages.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<Description>Default base stores for OpenIddict.</Description>
<Authors>Kévin Chalet</Authors>
<PackageTags>aspnetcore;authentication;jwt;openidconnect;openiddict;security</PackageTags>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\OpenIddict.Abstractions\OpenIddict.Abstractions.csproj" />
<ProjectReference Include="..\OpenIddict.Models\OpenIddict.Models.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="$(AspNetCoreVersion)" />
</ItemGroup>
</Project>

832
src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs

@ -1,832 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
namespace OpenIddict.Stores
{
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// Note: this base class can only be used with the default OpenIddict entities.
/// </summary>
/// <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="TKey">The type of the entity primary keys.</typeparam>
public abstract class OpenIddictApplicationStore<TApplication, TAuthorization, TToken, TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TKey : IEquatable<TKey>
{
protected OpenIddictApplicationStore([NotNull] IMemoryCache cache)
{
if (cache == null)
{
throw new ArgumentNullException(nameof(cache));
}
Cache = cache;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Determines the number of applications that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
{
return CountAsync(applications => applications, cancellationToken);
}
/// <summary>
/// Determines the number of applications that match the specified query.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications that match the specified query.
/// </returns>
public abstract Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TApplication>, IQueryable<TResult>> query, CancellationToken cancellationToken);
/// <summary>
/// Creates a new application.
/// </summary>
/// <param name="application">The application to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Removes an existing application.
/// </summary>
/// <param name="application">The application to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an application using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public virtual Task<TApplication> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
IQueryable<TApplication> Query(IQueryable<TApplication> applications, TKey key)
=> from application in applications
where application.Id.Equals(key)
select application;
return GetAsync((applications, key) => Query(applications, key), ConvertIdentifierFromString(identifier), cancellationToken);
}
/// <summary>
/// Retrieves an application using its client identifier.
/// </summary>
/// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public virtual Task<TApplication> FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
IQueryable<TApplication> Query(IQueryable<TApplication> applications, string id)
=> from application in applications
where application.ClientId == id
select application;
return GetAsync((applications, id) => Query(applications, id), identifier, cancellationToken);
}
/// <summary>
/// Retrieves all the applications associated with the specified post_logout_redirect_uri.
/// </summary>
/// <param name="address">The post_logout_redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
/// </returns>
public virtual async Task<ImmutableArray<TApplication>> FindByPostLogoutRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(address))
{
throw new ArgumentException("The address cannot be null or empty.", nameof(address));
}
// To optimize the efficiency of the query a bit, only applications whose stringified
// PostLogoutRedirectUris contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
IQueryable<TApplication> Query(IQueryable<TApplication> applications, string uri)
=> from application in applications
where application.PostLogoutRedirectUris.Contains(uri)
select application;
var builder = ImmutableArray.CreateBuilder<TApplication>();
foreach (var application in await ListAsync((applications, uri) => Query(applications, uri), address, cancellationToken))
{
foreach (var uri in await GetPostLogoutRedirectUrisAsync(application, cancellationToken))
{
// Note: the post_logout_redirect_uri must be compared
// using case-sensitive "Simple String Comparison".
if (string.Equals(uri, address, StringComparison.Ordinal))
{
builder.Add(application);
break;
}
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Retrieves all the applications associated with the specified redirect_uri.
/// </summary>
/// <param name="address">The redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
/// </returns>
public virtual async Task<ImmutableArray<TApplication>> FindByRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(address))
{
throw new ArgumentException("The address cannot be null or empty.", nameof(address));
}
// To optimize the efficiency of the query a bit, only applications whose stringified
// RedirectUris property contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
IQueryable<TApplication> Query(IQueryable<TApplication> applications, string uri)
=> from application in applications
where application.RedirectUris.Contains(uri)
select application;
var builder = ImmutableArray.CreateBuilder<TApplication>();
foreach (var application in await ListAsync((applications, uri) => Query(applications, uri), address, cancellationToken))
{
foreach (var uri in await GetRedirectUrisAsync(application, cancellationToken))
{
// Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison".
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
if (string.Equals(uri, address, StringComparison.Ordinal))
{
builder.Add(application);
break;
}
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public abstract Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client identifier associated with the application.
/// </returns>
public virtual ValueTask<string> GetClientIdAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ClientId);
}
/// <summary>
/// Retrieves the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client secret associated with the application.
/// </returns>
public virtual ValueTask<string> GetClientSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ClientSecret);
}
/// <summary>
/// Retrieves the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// </returns>
public virtual ValueTask<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.Type);
}
/// <summary>
/// Retrieves the consent type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the consent type of the application (by default, "explicit").
/// </returns>
public virtual ValueTask<string> GetConsentTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ConsentType);
}
/// <summary>
/// Retrieves the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// </returns>
public virtual ValueTask<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.DisplayName);
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(ConvertIdentifierToString(application.Id));
}
/// <summary>
/// Retrieves the permissions associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the permissions associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetPermissionsAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Permissions))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified permissions is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions);
var permissions = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Permissions)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(permissions);
}
/// <summary>
/// Retrieves the logout callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the post_logout_redirect_uri associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetPostLogoutRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.PostLogoutRedirectUris))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.PostLogoutRedirectUris)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(addresses);
}
/// <summary>
/// Retrieves the additional properties associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the application.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(application.Properties));
}
/// <summary>
/// Retrieves the callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the redirect_uri associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.RedirectUris))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.RedirectUris)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(addresses);
}
/// <summary>
/// Instantiates a new application.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated application, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TApplication> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TApplication>(new TApplication());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual Task<ImmutableArray<TApplication>> ListAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
IQueryable<TApplication> Query(IQueryable<TApplication> applications, int? skip, int? take)
{
var query = applications.OrderBy(application => application.Id).AsQueryable();
if (skip.HasValue)
{
query = query.Skip(skip.Value);
}
if (take.HasValue)
{
query = query.Take(take.Value);
}
return query;
}
return ListAsync((applications, state) => Query(applications, state.offset, state.count), (offset, count), cancellationToken);
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public abstract Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken);
/// <summary>
/// Sets the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientIdAsync([NotNull] TApplication application,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ClientId = identifier;
return Task.CompletedTask;
}
/// <summary>
/// Sets the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientSecretAsync([NotNull] TApplication application,
[CanBeNull] string secret, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ClientSecret = secret;
return Task.CompletedTask;
}
/// <summary>
/// Sets the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientTypeAsync([NotNull] TApplication application,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Sets the consent type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The consent type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetConsentTypeAsync([NotNull] TApplication application,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ConsentType = type;
return Task.CompletedTask;
}
/// <summary>
/// Sets the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="name">The display name associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDisplayNameAsync([NotNull] TApplication application,
[CanBeNull] string name, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.DisplayName = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the permissions associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="permissions">The permissions associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPermissionsAsync([NotNull] TApplication application, ImmutableArray<string> permissions, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (permissions.IsDefaultOrEmpty)
{
application.Permissions = null;
return Task.CompletedTask;
}
application.Permissions = new JArray(permissions.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the logout callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="addresses">The logout callback addresses associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPostLogoutRedirectUrisAsync([NotNull] TApplication application,
ImmutableArray<string> addresses, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (addresses.IsDefaultOrEmpty)
{
application.PostLogoutRedirectUris = null;
return Task.CompletedTask;
}
application.PostLogoutRedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="properties">The additional properties associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (properties == null)
{
application.Properties = null;
return Task.CompletedTask;
}
application.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="addresses">The callback addresses associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetRedirectUrisAsync([NotNull] TApplication application,
ImmutableArray<string> addresses, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (addresses.IsDefaultOrEmpty)
{
application.RedirectUris = null;
return Task.CompletedTask;
}
application.RedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing application.
/// </summary>
/// <param name="application">The application to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

694
src/OpenIddict.Stores/Stores/OpenIddictAuthorizationStore.cs

@ -1,694 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
namespace OpenIddict.Stores
{
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// Note: this base class can only be used with the default OpenIddict entities.
/// </summary>
/// <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="TKey">The type of the entity primary keys.</typeparam>
public abstract class OpenIddictAuthorizationStore<TAuthorization, TApplication, TToken, TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TKey : IEquatable<TKey>
{
protected OpenIddictAuthorizationStore([NotNull] IMemoryCache cache)
{
if (cache == null)
{
throw new ArgumentNullException(nameof(cache));
}
Cache = cache;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Determines the number of authorizations that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
{
return CountAsync(authorizations => authorizations, cancellationToken);
}
/// <summary>
/// Determines the number of authorizations that match the specified query.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations that match the specified query.
/// </returns>
public abstract Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TAuthorization>, IQueryable<TResult>> query, CancellationToken cancellationToken);
/// <summary>
/// Creates a new authorization.
/// </summary>
/// <param name="authorization">The authorization to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
/// <summary>
/// Removes an existing authorization.
/// </summary>
/// <param name="authorization">The authorization to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the authorizations corresponding to the specified
/// subject and associated with the application identifier.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the subject/client.
/// </returns>
public virtual Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
IQueryable<TAuthorization> Query(IQueryable<TAuthorization> authorizations, TKey key, string principal)
=> from authorization in authorizations
where authorization.Application != null &&
authorization.Application.Id.Equals(key) &&
authorization.Subject == principal
select authorization;
return ListAsync(
(authorizations, state) => Query(authorizations, state.key, state.principal),
(key: ConvertIdentifierFromString(client), principal: subject), cancellationToken);
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
IQueryable<TAuthorization> Query(IQueryable<TAuthorization> authorizations, TKey key, string principal, string state)
=> from authorization in authorizations
where authorization.Application != null &&
authorization.Application.Id.Equals(key) &&
authorization.Subject == principal &&
authorization.Status == state
select authorization;
return ListAsync(
(authorizations, state) => Query(authorizations, state.key, state.principal, state.state),
(key: ConvertIdentifierFromString(client), principal: subject, state: status), cancellationToken);
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="type">The authorization type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
IQueryable<TAuthorization> Query(IQueryable<TAuthorization> authorizations,
TKey key, string principal, string state, string kind)
=> from authorization in authorizations
where authorization.Application != null &&
authorization.Application.Id.Equals(key) &&
authorization.Subject == principal &&
authorization.Status == state &&
authorization.Type == kind
select authorization;
return ListAsync(
(authorizations, state) => Query(authorizations, state.key, state.principal, state.state, state.kind),
(key: ConvertIdentifierFromString(client), principal: subject, state: status, kind: type), cancellationToken);
}
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier.
/// </returns>
public virtual Task<TAuthorization> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
IQueryable<TAuthorization> Query(IQueryable<TAuthorization> authorizations, TKey key)
=> from authorization in authorizations
where authorization.Id.Equals(key)
select authorization;
return GetAsync((authorizations, key) => Query(authorizations, key), ConvertIdentifierFromString(identifier), cancellationToken);
}
/// <summary>
/// Retrieves all the authorizations corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the specified subject.
/// </returns>
public virtual Task<ImmutableArray<TAuthorization>> FindBySubjectAsync(
[NotNull] string subject, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
IQueryable<TAuthorization> Query(IQueryable<TAuthorization> authorizations, string principal)
=> from authorization in authorizations
where authorization.Subject == principal
select authorization;
return ListAsync((authorizations, principal) => Query(authorizations, principal), subject, cancellationToken);
}
/// <summary>
/// Retrieves the optional application identifier associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the authorization.
/// </returns>
public virtual ValueTask<string> GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (authorization.Application != null)
{
return new ValueTask<string>(ConvertIdentifierToString(authorization.Application.Id));
}
async Task<string> RetrieveApplicationIdAsync()
{
IQueryable<TKey> Query(IQueryable<TAuthorization> authorizations, TKey key)
=> from element in authorizations
where element.Id.Equals(key) &&
element.Application != null
select element.Application.Id;
return ConvertIdentifierToString(await GetAsync(
(authorizations, key) => Query(authorizations, key), authorization.Id, cancellationToken));
}
return new ValueTask<string>(RetrieveApplicationIdAsync());
}
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public abstract Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TAuthorization>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the authorization.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(ConvertIdentifierToString(authorization.Id));
}
/// <summary>
/// Retrieves the additional properties associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the authorization.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (string.IsNullOrEmpty(authorization.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(authorization.Properties));
}
/// <summary>
/// Retrieves the scopes associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes associated with the specified authorization.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetScopesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (string.IsNullOrEmpty(authorization.Scopes))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
return new ValueTask<ImmutableArray<string>>(JArray.Parse(authorization.Scopes).Select(element => (string) element).ToImmutableArray());
}
/// <summary>
/// Retrieves the status associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetStatusAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Status);
}
/// <summary>
/// Retrieves the subject associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetSubjectAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Subject);
}
/// <summary>
/// Retrieves the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the type associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetTypeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Type);
}
/// <summary>
/// Instantiates a new authorization.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated authorization, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TAuthorization> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TAuthorization>(new TAuthorization());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual Task<ImmutableArray<TAuthorization>> ListAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
IQueryable<TAuthorization> Query(IQueryable<TAuthorization> authorizations, int? skip, int? take)
{
var query = authorizations.OrderBy(authorization => authorization.Id).AsQueryable();
if (skip.HasValue)
{
query = query.Skip(skip.Value);
}
if (take.HasValue)
{
query = query.Take(take.Value);
}
return query;
}
return ListAsync((authorizations, state) => Query(authorizations, state.offset, state.count), (offset, count), cancellationToken);
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public abstract Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TAuthorization>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken);
/// <summary>
/// Removes the ad-hoc authorizations that are marked as invalid or have no valid token attached.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task PruneAsync(CancellationToken cancellationToken);
/// <summary>
/// Sets the application identifier associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="identifier">The unique identifier associated with the client application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task SetApplicationIdAsync([NotNull] TAuthorization authorization,
[CanBeNull] string identifier, CancellationToken cancellationToken);
/// <summary>
/// Sets the additional properties associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="properties">The additional properties associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (properties == null)
{
authorization.Properties = null;
return Task.CompletedTask;
}
authorization.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the scopes associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="scopes">The scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetScopesAsync([NotNull] TAuthorization authorization,
ImmutableArray<string> scopes, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (scopes.IsDefaultOrEmpty)
{
authorization.Scopes = null;
return Task.CompletedTask;
}
authorization.Scopes = new JArray(scopes.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the status associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="status">The status associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetStatusAsync([NotNull] TAuthorization authorization,
[CanBeNull] string status, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Status = status;
return Task.CompletedTask;
}
/// <summary>
/// Sets the subject associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetSubjectAsync([NotNull] TAuthorization authorization,
[CanBeNull] string subject, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Subject = subject;
return Task.CompletedTask;
}
/// <summary>
/// Sets the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="type">The type associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetTypeAsync([NotNull] TAuthorization authorization,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing authorization.
/// </summary>
/// <param name="authorization">The authorization to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

577
src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs

@ -1,577 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
namespace OpenIddict.Stores
{
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// Note: this base class can only be used with the default OpenIddict entities.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public abstract class OpenIddictScopeStore<TScope, TKey> : IOpenIddictScopeStore<TScope>
where TScope : OpenIddictScope<TKey>, new()
where TKey : IEquatable<TKey>
{
protected OpenIddictScopeStore([NotNull] IMemoryCache cache)
{
if (cache == null)
{
throw new ArgumentNullException(nameof(cache));
}
Cache = cache;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Determines the number of scopes that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
{
return CountAsync(scopes => scopes, cancellationToken);
}
/// <summary>
/// Determines the number of scopes that match the specified query.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes that match the specified query.
/// </returns>
public abstract Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken);
/// <summary>
/// Creates a new scope.
/// </summary>
/// <param name="scope">The scope to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken);
/// <summary>
/// Removes an existing scope.
/// </summary>
/// <param name="scope">The scope to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken);
/// <summary>
/// Retrieves a scope using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the identifier.
/// </returns>
public virtual Task<TScope> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
IQueryable<TScope> Query(IQueryable<TScope> scopes, TKey key)
=> from scope in scopes
where scope.Id.Equals(key)
select scope;
return GetAsync((scopes, key) => Query(scopes, key), ConvertIdentifierFromString(identifier), cancellationToken);
}
/// <summary>
/// Retrieves a scope using its name.
/// </summary>
/// <param name="name">The name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the specified name.
/// </returns>
public virtual Task<TScope> FindByNameAsync([NotNull] string name, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("The scope name cannot be null or empty.", nameof(name));
}
IQueryable<TScope> Query(IQueryable<TScope> scopes, string state)
=> from scope in scopes
where scope.Name == state
select scope;
return GetAsync((scopes, state) => Query(scopes, state), name, cancellationToken);
}
/// <summary>
/// Retrieves a list of scopes using their name.
/// </summary>
/// <param name="names">The names associated with the scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes corresponding to the specified names.
/// </returns>
public virtual Task<ImmutableArray<TScope>> FindByNamesAsync(
ImmutableArray<string> names, CancellationToken cancellationToken)
{
if (names.Any(name => string.IsNullOrEmpty(name)))
{
throw new ArgumentException("Scope names cannot be null or empty.", nameof(names));
}
IQueryable<TScope> Query(IQueryable<TScope> scopes, string[] values)
=> from scope in scopes
where values.Contains(scope.Name)
select scope;
return ListAsync((scopes, values) => Query(scopes, values), names.ToArray(), cancellationToken);
}
/// <summary>
/// Retrieves all the scopes that contain the specified resource.
/// </summary>
/// <param name="resource">The resource associated with the scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes associated with the specified resource.
/// </returns>
public virtual async Task<ImmutableArray<TScope>> FindByResourceAsync(
[NotNull] string resource, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(resource))
{
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource));
}
// To optimize the efficiency of the query a bit, only scopes whose stringified
// Resources column contains the specified resource are returned. Once the scopes
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
IQueryable<TScope> Query(IQueryable<TScope> scopes, string state)
=> from scope in scopes
where scope.Resources.Contains(state)
select scope;
var builder = ImmutableArray.CreateBuilder<TScope>();
foreach (var application in await ListAsync((applications, state) => Query(applications, state), resource, cancellationToken))
{
var resources = await GetResourcesAsync(application, cancellationToken);
if (resources.Contains(resource, StringComparer.OrdinalIgnoreCase))
{
builder.Add(application);
}
}
return builder.ToImmutable();
}
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public abstract Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the description associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the description associated with the specified scope.
/// </returns>
public virtual ValueTask<string> GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.Description);
}
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the scope.
/// </returns>
public virtual ValueTask<string> GetDisplayNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.DisplayName);
}
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the scope.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(ConvertIdentifierToString(scope.Id));
}
/// <summary>
/// Retrieves the name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the name associated with the specified scope.
/// </returns>
public virtual ValueTask<string> GetNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.Name);
}
/// <summary>
/// Retrieves the additional properties associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the scope.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(scope.Properties));
}
/// <summary>
/// Retrieves the resources associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the resources associated with the scope.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetResourcesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Resources))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified resources is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources);
var resources = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(scope.Resources)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(resources);
}
/// <summary>
/// Instantiates a new scope.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated scope, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TScope> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TScope>(new TScope());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual Task<ImmutableArray<TScope>> ListAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
IQueryable<TScope> Query(IQueryable<TScope> scopes, int? skip, int? take)
{
var query = scopes.OrderBy(scope => scope.Id).AsQueryable();
if (skip.HasValue)
{
query = query.Skip(skip.Value);
}
if (take.HasValue)
{
query = query.Take(take.Value);
}
return query;
}
return ListAsync((scopes, state) => Query(scopes, state.offset, state.count), (offset, count), cancellationToken);
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public abstract Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken);
/// <summary>
/// Sets the description associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="description">The description associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.Description = description;
return Task.CompletedTask;
}
/// <summary>
/// Sets the display name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="name">The display name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDisplayNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.DisplayName = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="name">The name associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.Name = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="properties">The additional properties associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (properties == null)
{
scope.Properties = null;
return Task.CompletedTask;
}
scope.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the resources associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="resources">The resources associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetResourcesAsync([NotNull] TScope scope, ImmutableArray<string> resources, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (resources.IsDefaultOrEmpty)
{
scope.Resources = null;
return Task.CompletedTask;
}
scope.Resources = new JArray(resources.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing scope.
/// </summary>
/// <param name="scope">The scope to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken);
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

805
src/OpenIddict.Stores/Stores/OpenIddictTokenStore.cs

@ -1,805 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
namespace OpenIddict.Stores
{
/// <summary>
/// Provides methods allowing to manage the tokens stored in a database.
/// Note: this base class can only be used with the default OpenIddict entities.
/// </summary>
/// <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="TKey">The type of the entity primary keys.</typeparam>
public abstract class OpenIddictTokenStore<TToken, TApplication, TAuthorization, TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>, new()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>, new()
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>, new()
where TKey : IEquatable<TKey>
{
protected OpenIddictTokenStore([NotNull] IMemoryCache cache)
{
if (cache == null)
{
throw new ArgumentNullException(nameof(cache));
}
Cache = cache;
}
/// <summary>
/// Gets the memory cached associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Determines the number of tokens that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database.
/// </returns>
public virtual Task<long> CountAsync(CancellationToken cancellationToken)
{
return CountAsync(tokens => tokens, cancellationToken);
}
/// <summary>
/// Determines the number of tokens that match the specified query.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of tokens that match the specified query.
/// </returns>
public abstract Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TToken>, IQueryable<TResult>> query, CancellationToken cancellationToken);
/// <summary>
/// Creates a new token.
/// </summary>
/// <param name="token">The token to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task CreateAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Removes a token.
/// </summary>
/// <param name="token">The token to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public abstract Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier.
/// </summary>
/// <param name="identifier">The application identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified application.
/// </returns>
public virtual Task<ImmutableArray<TToken>> FindByApplicationIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
IQueryable<TToken> Query(IQueryable<TToken> tokens, TKey key)
=> from token in tokens
where token.Application != null &&
token.Application.Id.Equals(key)
select token;
return ListAsync((tokens, key) => Query(tokens, key), ConvertIdentifierFromString(identifier), cancellationToken);
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified authorization identifier.
/// </summary>
/// <param name="identifier">The authorization identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified authorization.
/// </returns>
public virtual Task<ImmutableArray<TToken>> FindByAuthorizationIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
IQueryable<TToken> Query(IQueryable<TToken> tokens, TKey key)
=> from token in tokens
where token.Authorization != null &&
token.Authorization.Id.Equals(key)
select token;
return ListAsync((tokens, key) => Query(tokens, key), ConvertIdentifierFromString(identifier), cancellationToken);
}
/// <summary>
/// Retrieves a token using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier.
/// </returns>
public virtual Task<TToken> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
IQueryable<TToken> Query(IQueryable<TToken> tokens, TKey key)
=> from token in tokens
where token.Id.Equals(key)
select token;
return GetAsync((tokens, key) => Query(tokens, key), ConvertIdentifierFromString(identifier), cancellationToken);
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified reference identifier.
/// Note: the reference identifier may be hashed or encrypted for security reasons.
/// </summary>
/// <param name="identifier">The reference identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified reference identifier.
/// </returns>
public virtual Task<TToken> FindByReferenceIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
IQueryable<TToken> Query(IQueryable<TToken> tokens, string id)
=> from token in tokens
where token.ReferenceId == id
select token;
return GetAsync((tokens, id) => Query(tokens, identifier), identifier, cancellationToken);
}
/// <summary>
/// Retrieves the list of tokens corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified subject.
/// </returns>
public virtual Task<ImmutableArray<TToken>> FindBySubjectAsync([NotNull] string subject, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
IQueryable<TToken> Query(IQueryable<TToken> tokens, string principal)
=> from token in tokens
where token.Subject == principal
select token;
return ListAsync((tokens, principal) => Query(tokens, principal), subject, cancellationToken);
}
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public abstract Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TToken>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the optional application identifier associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the token.
/// </returns>
public virtual ValueTask<string> GetApplicationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (token.Application != null)
{
return new ValueTask<string>(ConvertIdentifierToString(token.Application.Id));
}
async Task<string> RetrieveApplicationIdAsync()
{
IQueryable<TKey> Query(IQueryable<TToken> tokens, TKey key)
=> from element in tokens
where element.Id.Equals(key) &&
element.Application != null
select element.Application.Id;
return ConvertIdentifierToString(await GetAsync((tokens, key) => Query(tokens, key), token.Id, cancellationToken));
}
return new ValueTask<string>(RetrieveApplicationIdAsync());
}
/// <summary>
/// Retrieves the optional authorization identifier associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization identifier associated with the token.
/// </returns>
public virtual ValueTask<string> GetAuthorizationIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (token.Authorization != null)
{
return new ValueTask<string>(ConvertIdentifierToString(token.Authorization.Id));
}
async Task<string> RetrieveAuthorizationIdAsync()
{
IQueryable<TKey> Query(IQueryable<TToken> tokens, TKey key)
=> from element in tokens
where element.Id.Equals(key) &&
element.Authorization != null
select element.Authorization.Id;
return ConvertIdentifierToString(await GetAsync((tokens, key) => Query(tokens, key), token.Id, cancellationToken));
}
return new ValueTask<string>(RetrieveAuthorizationIdAsync());
}
/// <summary>
/// Retrieves the creation date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified token.
/// </returns>
public virtual ValueTask<DateTimeOffset?> GetCreationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<DateTimeOffset?>(token.CreationDate);
}
/// <summary>
/// Retrieves the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the expiration date associated with the specified token.
/// </returns>
public virtual ValueTask<DateTimeOffset?> GetExpirationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<DateTimeOffset?>(token.ExpirationDate);
}
/// <summary>
/// Retrieves the unique identifier associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(ConvertIdentifierToString(token.Id));
}
/// <summary>
/// Retrieves the payload associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the payload associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetPayloadAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Payload);
}
/// <summary>
/// Retrieves the additional properties associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the token.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(token.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(token.Properties));
}
/// <summary>
/// Retrieves the reference identifier associated with a token.
/// Note: depending on the manager used to create the token,
/// the reference identifier may be hashed for security reasons.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the reference identifier associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetReferenceIdAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.ReferenceId);
}
/// <summary>
/// Retrieves the status associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetStatusAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Status);
}
/// <summary>
/// Retrieves the subject associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetSubjectAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Subject);
}
/// <summary>
/// Retrieves the token type associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token type associated with the specified token.
/// </returns>
public virtual ValueTask<string> GetTokenTypeAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return new ValueTask<string>(token.Type);
}
/// <summary>
/// Instantiates a new token.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated token, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TToken> InstantiateAsync(CancellationToken cancellationToken)
=> new ValueTask<TToken>(new TToken());
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual Task<ImmutableArray<TToken>> ListAsync([CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
IQueryable<TToken> Query(IQueryable<TToken> tokens, int? skip, int? take)
{
var query = tokens.OrderBy(token => token.Id).AsQueryable();
if (skip.HasValue)
{
query = query.Skip(skip.Value);
}
if (take.HasValue)
{
query = query.Take(take.Value);
}
return query;
}
return ListAsync((tokens, state) => Query(tokens, state.offset, state.count), (offset, count), cancellationToken);
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public abstract Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TToken>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken);
/// <summary>
/// Removes the tokens that are marked as expired or invalid.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task PruneAsync(CancellationToken cancellationToken);
/// <summary>
/// Sets the authorization identifier associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The unique identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task SetAuthorizationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken);
/// <summary>
/// Sets the application identifier associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The unique identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task SetApplicationIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken);
/// <summary>
/// Sets the creation date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="date">The creation date.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetCreationDateAsync([NotNull] TToken token,
[CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.CreationDate = date;
return Task.CompletedTask;
}
/// <summary>
/// Sets the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="date">The expiration date.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetExpirationDateAsync([NotNull] TToken token,
[CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.ExpirationDate = date;
return Task.CompletedTask;
}
/// <summary>
/// Sets the payload associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="payload">The payload associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPayloadAsync([NotNull] TToken token, [CanBeNull] string payload, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.Payload = payload;
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="properties">The additional properties associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TToken token, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (properties == null)
{
token.Properties = null;
return Task.CompletedTask;
}
token.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the reference identifier associated with a token.
/// Note: depending on the manager used to create the token,
/// the reference identifier may be hashed for security reasons.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="identifier">The reference identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetReferenceIdAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.ReferenceId = identifier;
return Task.CompletedTask;
}
/// <summary>
/// Sets the status associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="status">The status associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetStatusAsync([NotNull] TToken token, [CanBeNull] string status, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
token.Status = status;
return Task.CompletedTask;
}
/// <summary>
/// Sets the subject associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetSubjectAsync([NotNull] TToken token, [CanBeNull] string subject, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
token.Subject = subject;
return Task.CompletedTask;
}
/// <summary>
/// Sets the token type associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="type">The token type associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetTokenTypeAsync([NotNull] TToken token, [CanBeNull] string type, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The token type cannot be null or empty.", nameof(type));
}
token.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing token.
/// </summary>
/// <param name="token">The token to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public abstract Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

5
src/OpenIddict/OpenIddict.csproj

@ -14,13 +14,8 @@
<ItemGroup>
<ProjectReference Include="..\OpenIddict.Core\OpenIddict.Core.csproj" />
<ProjectReference Include="..\OpenIddict.Models\OpenIddict.Models.csproj" />
<ProjectReference Include="..\OpenIddict.Server\OpenIddict.Server.csproj" />
<ProjectReference Include="..\OpenIddict.Validation\OpenIddict.Validation.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" />
</ItemGroup>
</Project>

57
src/OpenIddict/OpenIddictExtensions.cs

@ -1,57 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using JetBrains.Annotations;
using OpenIddict.Models;
namespace Microsoft.Extensions.DependencyInjection
{
public static class OpenIddictExtensions
{
/// <summary>
/// Configures OpenIddict to use the default entities, with the default entity key type (string).
/// The default entities are <see cref="OpenIddictApplication"/>, <see cref="OpenIddictAuthorization"/>,
/// <see cref="OpenIddictScope"/> and <see cref="OpenIddictToken"/>.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public static OpenIddictCoreBuilder UseDefaultModels([NotNull] this OpenIddictCoreBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
return builder.UseCustomModels<OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken>();
}
/// <summary>
/// Configures OpenIddict to use the default entities, with the specified entity key type.
/// The default entities are <see cref="OpenIddictApplication{TKey}"/>, <see cref="OpenIddictAuthorization{TKey}"/>,
/// <see cref="OpenIddictScope{TKey}"/> and <see cref="OpenIddictToken{TKey}"/>.
/// </summary>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
/// <param name="builder">The services builder used by OpenIddict to register new services</param>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public static OpenIddictCoreBuilder UseDefaultModels<TKey>([NotNull] this OpenIddictCoreBuilder builder)
where TKey : IEquatable<TKey>
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
return builder.UseCustomModels<OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>>();
}
}
}

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

@ -356,15 +356,19 @@ namespace OpenIddict.Core.Tests
}
[Fact]
public void UseCustomModels_CustomEntitiesAreCorrectlySet()
public void RegisterDefaultModels_CustomEntitiesAreCorrectlySet()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
services.AddOpenIddict().AddCore()
.UseCustomModels<CustomApplication, CustomAuthorization, CustomScope, CustomToken>();
services.AddOpenIddict()
.AddCore()
.SetDefaultApplicationEntity<CustomApplication>()
.SetDefaultAuthorizationEntity<CustomAuthorization>()
.SetDefaultScopeEntity<CustomScope>()
.SetDefaultTokenEntity<CustomToken>();
// Assert
var provider = services.BuildServiceProvider();

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

@ -0,0 +1,57 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
using OpenIddict.EntityFramework;
using OpenIddict.EntityFramework.Models;
using Xunit;
namespace OpenIddict.EntityFramework.Tests
{
public class OpenIddictEntityFrameworkExtensionsTests
{
[Theory]
[InlineData(typeof(OpenIddictApplicationStoreResolver))]
[InlineData(typeof(OpenIddictAuthorizationStoreResolver))]
[InlineData(typeof(OpenIddictScopeStoreResolver))]
[InlineData(typeof(OpenIddictTokenStoreResolver))]
public void AddEntityFrameworkStores_RegistersEntityFrameworkStoreFactories(Type type)
{
// Arrange
var services = new ServiceCollection();
var builder = services.AddOpenIddict().AddCore();
// Act
builder.UseEntityFramework();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
}
[Fact]
public void UseEntityFrameworkModels_KeyTypeDefaultsToString()
{
// Arrange
var services = new ServiceCollection();
var builder = services.AddOpenIddict().AddCore();
// Act
builder.UseEntityFramework();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(OpenIddictApplication), options.DefaultApplicationType);
Assert.Equal(typeof(OpenIddictAuthorization), options.DefaultAuthorizationType);
Assert.Equal(typeof(OpenIddictScope), options.DefaultScopeType);
Assert.Equal(typeof(OpenIddictToken), options.DefaultTokenType);
}
}
}

35
test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs

@ -1,35 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Data.Entity;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.EntityFramework;
using Xunit;
namespace OpenIddict.EntityFrameworkCore.Tests
{
public class OpenIddictExtensionsTests
{
[Theory]
[InlineData(typeof(OpenIddictApplicationStoreResolver<DbContext>))]
[InlineData(typeof(OpenIddictAuthorizationStoreResolver<DbContext>))]
[InlineData(typeof(OpenIddictScopeStoreResolver<DbContext>))]
[InlineData(typeof(OpenIddictTokenStoreResolver<DbContext>))]
public void AddEntityFrameworkStores_RegistersEntityFrameworkStoreFactories(Type type)
{
// Arrange
var services = new ServiceCollection();
var builder = services.AddOpenIddict().AddCore();
// Act
builder.AddEntityFrameworkStores<DbContext>();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
}
}
}

32
test/OpenIddict.Tests/OpenIddictExtensionsTests.cs → test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreExtensionsTests.cs

@ -8,22 +8,40 @@ using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Core;
using OpenIddict.Models;
using OpenIddict.EntityFrameworkCore.Models;
using Xunit;
namespace OpenIddict.Tests
namespace OpenIddict.EntityFrameworkCore.Tests
{
public class OpenIddictExtensionsTests
public class OpenIddictEntityFrameworkCoreExtensionsTests
{
[Theory]
[InlineData(typeof(OpenIddictApplicationStoreResolver))]
[InlineData(typeof(OpenIddictAuthorizationStoreResolver))]
[InlineData(typeof(OpenIddictScopeStoreResolver))]
[InlineData(typeof(OpenIddictTokenStoreResolver))]
public void UseEntityFrameworkCore_RegistersEntityFrameworkCoreStoreFactories(Type type)
{
// Arrange
var services = new ServiceCollection();
var builder = services.AddOpenIddict().AddCore();
// Act
builder.UseEntityFrameworkCore();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
}
[Fact]
public void UseDefaultModels_KeyTypeDefaultsToString()
public void UseEntityFrameworkCore_KeyTypeDefaultsToString()
{
// Arrange
var services = new ServiceCollection();
var builder = services.AddOpenIddict().AddCore();
// Act
builder.UseDefaultModels();
builder.UseEntityFrameworkCore();
// Assert
var provider = services.BuildServiceProvider();
@ -36,14 +54,14 @@ namespace OpenIddict.Tests
}
[Fact]
public void UseDefaultModels_KeyTypeCanBeOverriden()
public void UseEntityFrameworkCore_KeyTypeCanBeOverriden()
{
// Arrange
var services = new ServiceCollection();
var builder = services.AddOpenIddict().AddCore();
// Act
builder.UseDefaultModels<Guid>();
builder.UseEntityFrameworkCore().ReplaceDefaultEntities<Guid>();
// Assert
var provider = services.BuildServiceProvider();

34
test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs

@ -1,34 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace OpenIddict.EntityFrameworkCore.Tests
{
public class OpenIddictExtensionsTests
{
[Theory]
[InlineData(typeof(OpenIddictApplicationStoreResolver<DbContext>))]
[InlineData(typeof(OpenIddictAuthorizationStoreResolver<DbContext>))]
[InlineData(typeof(OpenIddictScopeStoreResolver<DbContext>))]
[InlineData(typeof(OpenIddictTokenStoreResolver<DbContext>))]
public void AddEntityFrameworkCoreStores_RegistersEntityFrameworkCoreStoreFactories(Type type)
{
// Arrange
var services = new ServiceCollection();
var builder = services.AddOpenIddict().AddCore();
// Act
builder.AddEntityFrameworkCoreStores<DbContext>();
// Assert
Assert.Contains(services, service => service.ImplementationType == type);
}
}
}

14
test/OpenIddict.Server.Tests/Internal/OpenIddictServerInitializerTests.cs

@ -317,7 +317,14 @@ namespace OpenIddict.Server.Tests
services.AddDistributedMemoryCache();
services.AddOpenIddict()
.AddCore(options => options.UseDefaultModels())
.AddCore(options =>
{
options.SetDefaultApplicationEntity<OpenIddictApplication>()
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>()
.SetDefaultScopeEntity<OpenIddictScope>()
.SetDefaultTokenEntity<OpenIddictToken>();
})
.AddServer(options => configuration?.Invoke(options));
});
@ -330,5 +337,10 @@ namespace OpenIddict.Server.Tests
return new TestServer(builder);
}
public class OpenIddictApplication { }
public class OpenIddictAuthorization { }
public class OpenIddictScope { }
public class OpenIddictToken { }
}
}

1
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs

@ -16,7 +16,6 @@ using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using OpenIddict.Abstractions;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Server.Tests

1
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Exchange.cs

@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Server.Tests

1
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Introspection.cs

@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Server.Tests

1
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Revocation.cs

@ -17,7 +17,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Moq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Server.Tests

76
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Serialization.cs

@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Abstractions;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Server.Tests
@ -1584,11 +1583,7 @@ namespace OpenIddict.Server.Tests
public async Task SerializeAccessToken_ReferenceAccessTokenIsCorrectlyPersisted()
{
// Arrange
var token = new OpenIddictToken
{
CreationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero)
};
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
@ -1610,8 +1605,9 @@ namespace OpenIddict.Server.Tests
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.AccessTokenLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero));
options.AccessTokenLifetime = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero) -
new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero);
});
});
@ -1634,8 +1630,8 @@ namespace OpenIddict.Server.Tests
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.ExpirationDate == new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero) &&
descriptor.CreationDate == new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero) &&
descriptor.Payload != null &&
descriptor.ReferenceId != null &&
descriptor.Subject == "Bob le Magnifique" &&
@ -1821,11 +1817,7 @@ namespace OpenIddict.Server.Tests
public async Task SerializeAuthorizationCode_AuthorizationCodeIsCorrectlyPersisted()
{
// Arrange
var token = new OpenIddictToken
{
CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
@ -1867,8 +1859,9 @@ namespace OpenIddict.Server.Tests
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.AuthorizationCodeLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero));
options.AuthorizationCodeLifetime = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero) -
new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero);
});
});
@ -1887,8 +1880,8 @@ namespace OpenIddict.Server.Tests
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.ExpirationDate == new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero) &&
descriptor.CreationDate == new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero) &&
descriptor.Payload == null &&
descriptor.ReferenceId == null &&
descriptor.Subject == "Bob le Magnifique" &&
@ -1900,11 +1893,7 @@ namespace OpenIddict.Server.Tests
public async Task SerializeAuthorizationCode_ReferenceAuthorizationCodeIsCorrectlyPersisted()
{
// Arrange
var token = new OpenIddictToken
{
CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
@ -1951,8 +1940,9 @@ namespace OpenIddict.Server.Tests
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.AuthorizationCodeLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero));
options.AuthorizationCodeLifetime = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero) -
new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero);
});
});
@ -1974,8 +1964,8 @@ namespace OpenIddict.Server.Tests
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.ExpirationDate == new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero) &&
descriptor.CreationDate == new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero) &&
descriptor.Payload != null &&
descriptor.ReferenceId != null &&
descriptor.Subject == "Bob le Magnifique" &&
@ -2232,11 +2222,7 @@ namespace OpenIddict.Server.Tests
public async Task SerializeRefreshToken_RefreshTokenIsCorrectlyPersisted()
{
// Arrange
var token = new OpenIddictToken
{
CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
@ -2253,8 +2239,9 @@ namespace OpenIddict.Server.Tests
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.RefreshTokenLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero));
options.RefreshTokenLifetime = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero) -
new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero);
});
});
@ -2274,8 +2261,8 @@ namespace OpenIddict.Server.Tests
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.ExpirationDate == new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero) &&
descriptor.CreationDate == new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero) &&
descriptor.Payload == null &&
descriptor.ReferenceId == null &&
descriptor.Subject == "Bob le Magnifique" &&
@ -2287,11 +2274,7 @@ namespace OpenIddict.Server.Tests
public async Task SerializeRefreshToken_ReferenceRefreshTokenIsCorrectlyPersisted()
{
// Arrange
var token = new OpenIddictToken
{
CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
@ -2313,8 +2296,9 @@ namespace OpenIddict.Server.Tests
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.RefreshTokenLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero));
options.RefreshTokenLifetime = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero) -
new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero);
});
});
@ -2337,8 +2321,8 @@ namespace OpenIddict.Server.Tests
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.ExpirationDate == new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero) &&
descriptor.CreationDate == new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero) &&
descriptor.Payload != null &&
descriptor.ReferenceId == "B1F0D503-55A4-4B03-B05B-EF07713C18E1" &&
descriptor.Subject == "Bob le Magnifique" &&

11
test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs

@ -30,7 +30,6 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Server.Tests
@ -1347,7 +1346,10 @@ namespace OpenIddict.Server.Tests
services.AddOpenIddict()
.AddCore(options =>
{
options.UseDefaultModels();
options.SetDefaultApplicationEntity<OpenIddictApplication>()
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>()
.SetDefaultScopeEntity<OpenIddictScope>()
.SetDefaultTokenEntity<OpenIddictToken>();
options.Services.AddSingleton(CreateApplicationManager());
options.Services.AddSingleton(CreateAuthorizationManager());
@ -1546,5 +1548,10 @@ namespace OpenIddict.Server.Tests
return manager.Object;
}
public class OpenIddictApplication { }
public class OpenIddictAuthorization { }
public class OpenIddictScope { }
public class OpenIddictToken { }
}
}

14
test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs

@ -671,7 +671,14 @@ namespace OpenIddict.Server.Tests
private static OpenIddictServerBuilder CreateBuilder(IServiceCollection services)
=> services.AddOpenIddict()
.AddCore(options => options.UseDefaultModels())
.AddCore(options =>
{
options.SetDefaultApplicationEntity<OpenIddictApplication>()
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>()
.SetDefaultScopeEntity<OpenIddictScope>()
.SetDefaultTokenEntity<OpenIddictToken>();
})
.AddServer();
private static IServiceCollection CreateServices()
@ -695,5 +702,10 @@ namespace OpenIddict.Server.Tests
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>();
return options.Get(OpenIddictServerDefaults.AuthenticationScheme);
}
public class OpenIddictApplication { }
public class OpenIddictAuthorization { }
public class OpenIddictScope { }
public class OpenIddictToken { }
}
}

27
test/OpenIddict.Tests/OpenIddict.Tests.csproj

@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\tests.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TargetFrameworks Condition=" $([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToReferenceAssemblies('.NETFramework', '4.7', '').Count) != 0 ">$(TargetFrameworks);net47</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict\OpenIddict.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="Moq" Version="$(MoqVersion)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

11
test/OpenIddict.Validation.Tests/OpenIddictValidationHandlerTests.cs

@ -27,7 +27,6 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Validation.Tests
@ -687,6 +686,11 @@ namespace OpenIddict.Validation.Tests
services.AddOpenIddict()
.AddCore(options =>
{
options.SetDefaultApplicationEntity<OpenIddictApplication>()
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>()
.SetDefaultScopeEntity<OpenIddictScope>()
.SetDefaultTokenEntity<OpenIddictToken>();
// Replace the default OpenIddict managers.
options.Services.AddSingleton(CreateApplicationManager());
options.Services.AddSingleton(CreateAuthorizationManager());
@ -824,5 +828,10 @@ namespace OpenIddict.Validation.Tests
return manager.Object;
}
public class OpenIddictApplication { }
public class OpenIddictAuthorization { }
public class OpenIddictScope { }
public class OpenIddictToken { }
}
}

Loading…
Cancel
Save