Browse Source

Add navigation properties to the new session entity and implement pruning support

pull/2537/head
Kévin Chalet 2 weeks ago
parent
commit
6665db782e
  1. 20
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
  2. 20
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs
  3. 2
      src/OpenIddict.Abstractions/Caches/IOpenIddictSessionCache.cs
  4. 14
      src/OpenIddict.Abstractions/Managers/IOpenIddictSessionManager.cs
  5. 6
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  6. 14
      src/OpenIddict.Abstractions/Stores/IOpenIddictSessionStore.cs
  7. 2
      src/OpenIddict.Core/Caches/OpenIddictSessionCache.cs
  8. 21
      src/OpenIddict.Core/Managers/OpenIddictSessionManager.cs
  9. 9
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs
  10. 9
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs
  11. 11
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkSession.cs
  12. 11
      src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs
  13. 17
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkApplicationConfiguration.cs
  14. 15
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkAuthorizationConfiguration.cs
  15. 16
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkSessionConfiguration.cs
  16. 9
      src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkTokenConfiguration.cs
  17. 14
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs
  18. 14
      src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs
  19. 87
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkApplicationStore.cs
  20. 86
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs
  21. 4
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkResourceStore.cs
  22. 4
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkScopeStore.cs
  23. 139
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkSessionStore.cs
  24. 34
      src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs
  25. 10
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs
  26. 12
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs
  27. 14
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreSession.cs
  28. 14
      src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs
  29. 14
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreApplicationConfiguration.cs
  30. 15
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreAuthorizationConfiguration.cs
  31. 14
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreSessionConfiguration.cs
  32. 9
      src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreTokenConfiguration.cs
  33. 14
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs
  34. 8
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs
  35. 30
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
  36. 75
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreApplicationStore.cs
  37. 58
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
  38. 4
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreResourceStore.cs
  39. 4
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreScopeStore.cs
  40. 190
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreSessionStore.cs
  41. 37
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
  42. 6
      src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs
  43. 4
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbApplicationStore.cs
  44. 4
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
  45. 42
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbSessionStore.cs
  46. 2
      src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs
  47. 19
      src/OpenIddict.Quartz/OpenIddictQuartzBuilder.cs
  48. 48
      src/OpenIddict.Quartz/OpenIddictQuartzJob.cs
  49. 11
      src/OpenIddict.Quartz/OpenIddictQuartzOptions.cs
  50. 1
      src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
  51. 1
      src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
  52. 1
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  53. 1
      src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
  54. 1
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  55. 14
      test/OpenIddict.EntityFramework.Tests/OpenIddictEntityFrameworkBuilderTests.cs
  56. 14
      test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreBuilderTests.cs
  57. 26
      test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreHelpersTests.cs
  58. 45
      test/OpenIddict.Quartz.Tests/OpenIddictQuartzBuilderTests.cs
  59. 124
      test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs

20
sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs

@ -204,14 +204,16 @@ public class AuthorizationController : Controller
{
var sessions = await _sessionManager.FindAsync(
query: (
Subject : user.Id,
LoginId : result.Identity.GetClaim("login_id"),
ApplicationId: await _applicationManager.GetIdAsync(application),
Status : Statuses.Valid)).ToListAsync();
Subject : user.Id,
LoginId : result.Identity.GetClaim("login_id"),
ApplicationId : await _applicationManager.GetIdAsync(application),
AuthorizationId: await _authorizationManager.GetIdAsync(authorization),
Status : Statuses.Valid)).ToListAsync();
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{
ApplicationId = await _applicationManager.GetIdAsync(application),
AuthorizationId = await _authorizationManager.GetIdAsync(authorization),
LoginId = result.Identity.GetClaim("login_id"),
Subject = user.Id
});
@ -354,14 +356,16 @@ public class AuthorizationController : Controller
{
var sessions = await _sessionManager.FindAsync(
query: (
Subject : user.Id,
LoginId : result.Identity.GetClaim("login_id"),
ApplicationId: await _applicationManager.GetIdAsync(application),
Status : Statuses.Valid)).ToListAsync();
Subject : user.Id,
LoginId : result.Identity.GetClaim("login_id"),
ApplicationId : await _applicationManager.GetIdAsync(application),
AuthorizationId: await _authorizationManager.GetIdAsync(authorization),
Status : Statuses.Valid)).ToListAsync();
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{
ApplicationId = await _applicationManager.GetIdAsync(application),
AuthorizationId = await _authorizationManager.GetIdAsync(authorization),
LoginId = result.Identity.GetClaim("login_id"),
Subject = user.Id
});

20
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs

@ -233,14 +233,16 @@ public class AuthorizationController : Controller
{
var sessions = await _sessionManager.FindAsync(
query: (
Subject : await _userManager.GetUserIdAsync(user),
LoginId : result.Principal.GetClaim("login_id"),
ApplicationId: await _applicationManager.GetIdAsync(application),
Status : Statuses.Valid)).ToListAsync();
Subject : await _userManager.GetUserIdAsync(user),
LoginId : result.Principal.GetClaim("login_id"),
ApplicationId : await _applicationManager.GetIdAsync(application),
AuthorizationId: await _authorizationManager.GetIdAsync(authorization),
Status : Statuses.Valid)).ToListAsync();
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{
ApplicationId = await _applicationManager.GetIdAsync(application),
AuthorizationId = await _authorizationManager.GetIdAsync(authorization),
LoginId = result.Principal.GetClaim("login_id"),
Subject = await _userManager.GetUserIdAsync(user)
});
@ -365,14 +367,16 @@ public class AuthorizationController : Controller
{
var sessions = await _sessionManager.FindAsync(
query: (
Subject : await _userManager.GetUserIdAsync(user),
LoginId : User.GetClaim("login_id"),
ApplicationId: await _applicationManager.GetIdAsync(application),
Status : Statuses.Valid)).ToListAsync();
Subject : await _userManager.GetUserIdAsync(user),
LoginId : User.GetClaim("login_id"),
ApplicationId : await _applicationManager.GetIdAsync(application),
AuthorizationId: await _authorizationManager.GetIdAsync(authorization),
Status : Statuses.Valid)).ToListAsync();
var session = sessions.LastOrDefault() ?? await _sessionManager.CreateAsync(new()
{
ApplicationId = await _applicationManager.GetIdAsync(application),
AuthorizationId = await _authorizationManager.GetIdAsync(authorization),
LoginId = User.GetClaim("login_id"),
Subject = await _userManager.GetUserIdAsync(user)
});

2
src/OpenIddict.Abstractions/Caches/IOpenIddictSessionCache.cs

@ -27,7 +27,7 @@ public interface IOpenIddictSessionCache<TSession> where TSession : class
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the criteria.</returns>
IAsyncEnumerable<TSession> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query, CancellationToken cancellationToken);
(string? Subject, string? LoginId, string? ApplicationId, string? AuthorizationId, string? Status) query, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified application identifier.

14
src/OpenIddict.Abstractions/Managers/IOpenIddictSessionManager.cs

@ -95,7 +95,7 @@ public interface IOpenIddictSessionManager
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the criteria.</returns>
IAsyncEnumerable<object> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query,
(string? Subject, string? LoginId, string? ApplicationId, string? AuthorizationId, string? Status) query,
CancellationToken cancellationToken = default);
/// <summary>
@ -313,6 +313,18 @@ public interface IOpenIddictSessionManager
/// </returns>
ValueTask PopulateAsync(object session, OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken = default);
/// <summary>
/// Removes the sessions that are marked as invalid and don't have any token attached.
/// Only sessions created before the specified <paramref name="threshold"/> are removed.
/// </summary>
/// <remarks>
/// Since sessions with tokens still attached are not deleted, tokens should always be pruned first.
/// </remarks>
/// <param name="threshold">The date before which sessions are not pruned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of sessions that were removed.</returns>
ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken);
/// <summary>
/// Updates an existing session.
/// </summary>

6
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -855,15 +855,9 @@ Reload the entity from the database and retry the operation.</value>
<value>An error occurred while trying to create a new entity instance.
Make sure that the entity is not abstract and has a public parameterless constructor or create a custom store that overrides 'InstantiateAsync()' to use a custom factory.</value>
</data>
<data name="ID0243" xml:space="preserve">
<value>An error occurred while pruning authorizations.</value>
</data>
<data name="ID0244" xml:space="preserve">
<value>The application matching the specified identifier cannot be found in the change tracker or in the database.</value>
</data>
<data name="ID0249" xml:space="preserve">
<value>An error occurred while pruning tokens.</value>
</data>
<data name="ID0251" xml:space="preserve">
<value>The authorization matching the specified identifier cannot be found in the change tracker or in the database.</value>
</data>

14
src/OpenIddict.Abstractions/Stores/IOpenIddictSessionStore.cs

@ -64,7 +64,7 @@ public interface IOpenIddictSessionStore<TSession> where TSession : class
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the criteria.</returns>
IAsyncEnumerable<TSession> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query, CancellationToken cancellationToken);
(string? Subject, string? LoginId, string? ApplicationId, string? AuthorizationId, string? Status) query, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the list of sessions corresponding to the specified application identifier.
@ -245,6 +245,18 @@ public interface IOpenIddictSessionStore<TSession> where TSession : class
Func<IQueryable<TSession>, TState, IQueryable<TResult>> query,
TState state, CancellationToken cancellationToken);
/// <summary>
/// Removes the sessions that are marked as invalid and don't have any token attached.
/// Only sessions created before the specified <paramref name="threshold"/> are removed.
/// </summary>
/// <remarks>
/// Since sessions with tokens still attached are not deleted, tokens should always be pruned first.
/// </remarks>
/// <param name="threshold">The date before which sessions are not pruned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of sessions that were removed.</returns>
ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken);
/// <summary>
/// Sets the application identifier associated with a session.
/// </summary>

2
src/OpenIddict.Core/Caches/OpenIddictSessionCache.cs

@ -96,7 +96,7 @@ public sealed class OpenIddictSessionCache<TSession> : IOpenIddictSessionCache<T
/// <inheritdoc/>
public async IAsyncEnumerable<TSession> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query,
(string? Subject, string? LoginId, string? ApplicationId, string? AuthorizationId, string? Status) query,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
// Note: this method is only partially cached.

21
src/OpenIddict.Core/Managers/OpenIddictSessionManager.cs

@ -217,7 +217,7 @@ public class OpenIddictSessionManager<TSession> : IOpenIddictSessionManager wher
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The sessions corresponding to the criteria.</returns>
public virtual IAsyncEnumerable<TSession> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query,
(string? Subject, string? LoginId, string? ApplicationId, string? AuthorizationId, string? Status) query,
CancellationToken cancellationToken = default)
{
var sessions = Options.CurrentValue.DisableEntityCaching
@ -699,6 +699,19 @@ public class OpenIddictSessionManager<TSession> : IOpenIddictSessionManager wher
}
}
/// <summary>
/// Removes the sessions that are marked as invalid and don't have any token attached.
/// Only sessions created before the specified <paramref name="threshold"/> are removed.
/// </summary>
/// <remarks>
/// Since sessions with tokens still attached are not deleted, tokens should always be pruned first.
/// </remarks>
/// <param name="threshold">The date before which sessions are not pruned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of sessions that were removed.</returns>
public virtual ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
=> Store.PruneAsync(threshold, cancellationToken);
/// <summary>
/// Updates an existing session.
/// </summary>
@ -822,7 +835,7 @@ public class OpenIddictSessionManager<TSession> : IOpenIddictSessionManager wher
=> DeleteAsync((TSession) session, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictSessionManager.FindAsync((string? Subject, string? LoginId, string? ApplicationId, string? Status) query, CancellationToken cancellationToken)
IAsyncEnumerable<object> IOpenIddictSessionManager.FindAsync((string? Subject, string? LoginId, string? ApplicationId, string? AuthorizationId, string? Status) query, CancellationToken cancellationToken)
=> FindAsync(query, cancellationToken);
/// <inheritdoc/>
@ -905,6 +918,10 @@ public class OpenIddictSessionManager<TSession> : IOpenIddictSessionManager wher
ValueTask IOpenIddictSessionManager.PopulateAsync(object session, OpenIddictSessionDescriptor descriptor, CancellationToken cancellationToken)
=> PopulateAsync((TSession) session, descriptor, cancellationToken);
/// <inheritdoc/>
ValueTask<long> IOpenIddictSessionManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
=> PruneAsync(threshold, cancellationToken);
/// <inheritdoc/>
ValueTask IOpenIddictSessionManager.UpdateAsync(object session, CancellationToken cancellationToken)
=> UpdateAsync((TSession) session, cancellationToken);

9
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkApplication.cs

@ -15,6 +15,7 @@ namespace OpenIddict.EntityFramework.Models;
public class OpenIddictEntityFrameworkApplication :
OpenIddictEntityFrameworkApplication<string,
OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkSession,
OpenIddictEntityFrameworkToken>
{
public OpenIddictEntityFrameworkApplication() => Id = Guid.NewGuid().ToString();
@ -24,9 +25,10 @@ public class OpenIddictEntityFrameworkApplication :
/// Represents an OpenIddict application.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; ClientId = {ClientId,nq} ; ClientType = {ClientType,nq}")]
public class OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
public class OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TKey : notnull, IEquatable<TKey>
where TAuthorization : class
where TSession : class
where TToken : class
{
/// <summary>
@ -134,6 +136,11 @@ public class OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
[StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Settings { get; set; }
/// <summary>
/// Gets the list of the sessions associated with the application.
/// </summary>
public virtual ICollection<TSession> Sessions { get; } = new HashSet<TSession>();
/// <summary>
/// Gets the list of the tokens associated with the application.
/// </summary>

9
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkAuthorization.cs

@ -15,6 +15,7 @@ namespace OpenIddict.EntityFramework.Models;
public class OpenIddictEntityFrameworkAuthorization :
OpenIddictEntityFrameworkAuthorization<string,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkSession,
OpenIddictEntityFrameworkToken>
{
public OpenIddictEntityFrameworkAuthorization() => Id = Guid.NewGuid().ToString();
@ -24,9 +25,10 @@ public class OpenIddictEntityFrameworkAuthorization :
/// Represents an OpenIddict authorization.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")]
public class OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
public class OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TKey : notnull, IEquatable<TKey>
where TApplication : class
where TSession : class
where TToken : class
{
/// <summary>
@ -61,6 +63,11 @@ public class OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
[StringSyntax(StringSyntaxAttribute.Json)]
public virtual string? Scopes { get; set; }
/// <summary>
/// Gets the list of the sessions associated with the authorization.
/// </summary>
public virtual ICollection<TSession> Sessions { get; } = new HashSet<TSession>();
/// <summary>
/// Gets or sets the status of the authorization.
/// </summary>

11
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkSession.cs

@ -15,7 +15,8 @@ namespace OpenIddict.EntityFramework.Models;
public class OpenIddictEntityFrameworkSession :
OpenIddictEntityFrameworkSession<string,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkAuthorization>
OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkToken>
{
public OpenIddictEntityFrameworkSession() => Id = Guid.NewGuid().ToString();
}
@ -24,10 +25,11 @@ public class OpenIddictEntityFrameworkSession :
/// Represents an OpenIddict session.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; LoginId = {LoginId,nq} ; Status = {Status,nq}")]
public class OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization>
public class OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
where TApplication : class
where TAuthorization : class
where TToken : class
{
/// <summary>
/// Gets or sets the application associated with the session.
@ -74,4 +76,9 @@ public class OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization
/// Gets or sets the subject of the session.
/// </summary>
public virtual string? Subject { get; set; }
/// <summary>
/// Gets the list of the tokens associated with the session.
/// </summary>
public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>();
}

11
src/OpenIddict.EntityFramework.Models/OpenIddictEntityFrameworkToken.cs

@ -15,7 +15,8 @@ namespace OpenIddict.EntityFramework.Models;
public class OpenIddictEntityFrameworkToken :
OpenIddictEntityFrameworkToken<string,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkAuthorization>
OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkSession>
{
public OpenIddictEntityFrameworkToken() => Id = Guid.NewGuid().ToString();
}
@ -24,10 +25,11 @@ public class OpenIddictEntityFrameworkToken :
/// Represents an OpenIddict token.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")]
public class OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
public class OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
where TApplication : class
where TAuthorization : class
where TSession : class
{
/// <summary>
/// Gets or sets the application associated with the token.
@ -88,6 +90,11 @@ public class OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
/// </remarks>
public virtual string? ReferenceId { get; set; }
/// <summary>
/// Gets or sets the session associated with the token.
/// </summary>
public virtual TSession? Session { get; set; }
/// <summary>
/// Gets or sets the status of the token.
/// </summary>

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

@ -17,17 +17,20 @@ namespace OpenIddict.EntityFramework;
/// </summary>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkApplicationConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : EntityTypeConfiguration<TApplication>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkApplicationConfiguration()
@ -75,6 +78,14 @@ public sealed class OpenIddictEntityFrameworkApplicationConfiguration<
Property(lambda).HasMaxLength(100);
}
HasMany(static application => application.Sessions)
.WithOptional(static session => session.Application!)
.Map(static association =>
{
association.MapKey(nameof(OpenIddictEntityFrameworkSession.Application) +
nameof(OpenIddictEntityFrameworkApplication.Id));
});
HasMany(static application => application.Tokens)
.WithOptional(static token => token.Application!)
.Map(static association =>

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

@ -17,17 +17,20 @@ namespace OpenIddict.EntityFramework;
/// </summary>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkAuthorizationConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : EntityTypeConfiguration<TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkAuthorizationConfiguration()
@ -52,6 +55,12 @@ public sealed class OpenIddictEntityFrameworkAuthorizationConfiguration<
Property(lambda).HasMaxLength(100);
}
HasMany(static authorization => authorization.Sessions)
.WithOptional(static session => session.Authorization!)
.Map(static association => association.MapKey(nameof(OpenIddictEntityFrameworkSession.Authorization) +
nameof(OpenIddictEntityFrameworkAuthorization.Id)))
.WillCascadeOnDelete();
Property(static authorization => authorization.Status)
.HasMaxLength(50);

16
src/OpenIddict.EntityFramework/Configurations/OpenIddictEntityFrameworkSessionConfiguration.cs

@ -27,10 +27,10 @@ public sealed class OpenIddictEntityFrameworkSessionConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : EntityTypeConfiguration<TSession>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkSessionConfiguration()
@ -66,6 +66,14 @@ public sealed class OpenIddictEntityFrameworkSessionConfiguration<
Property(static session => session.Subject)
.HasMaxLength(400);
HasMany(static session => session.Tokens)
.WithOptional(static token => token.Session!)
.Map(static association =>
{
association.MapKey(nameof(OpenIddictEntityFrameworkToken.Session) +
nameof(OpenIddictEntityFrameworkSession.Id));
});
ToTable("OpenIddictSessions");
}
}

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

@ -18,16 +18,19 @@ namespace OpenIddict.EntityFramework;
/// <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="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkTokenConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : EntityTypeConfiguration<TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkTokenConfiguration()

14
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkBuilder.cs

@ -71,12 +71,12 @@ public sealed class OpenIddictEntityFrameworkBuilder
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>()
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TResource : OpenIddictEntityFrameworkResource<TKey>
where TScope : OpenIddictEntityFrameworkScope<TKey>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
// Note: unlike Entity Framework Core, Entity Framework 6.x always
@ -113,9 +113,9 @@ public sealed class OpenIddictEntityFrameworkBuilder
provider.GetRequiredService<OpenIddictTokenManager<TToken>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictApplicationStore<TApplication>,
OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorization, TToken, TKey>>());
OpenIddictEntityFrameworkApplicationStore<TApplication, TAuthorization, TSession, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictAuthorizationStore<TAuthorization>,
OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplication, TToken, TKey>>());
OpenIddictEntityFrameworkAuthorizationStore<TAuthorization, TApplication, TSession, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictResourceStore<TResource>,
OpenIddictEntityFrameworkResourceStore<TResource, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeStore<TScope>,
@ -123,7 +123,7 @@ public sealed class OpenIddictEntityFrameworkBuilder
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictSessionStore<TSession>,
OpenIddictEntityFrameworkSessionStore<TSession, TApplication, TAuthorization, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenStore<TToken>,
OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthorization, TKey>>());
OpenIddictEntityFrameworkTokenStore<TToken, TApplication, TAuthorization, TSession, TKey>>());
return this;
}

14
src/OpenIddict.EntityFramework/OpenIddictEntityFrameworkHelpers.cs

@ -48,23 +48,23 @@ public static class OpenIddictEntityFrameworkHelpers
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this DbModelBuilder builder)
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TResource : OpenIddictEntityFrameworkResource<TKey>
where TScope : OpenIddictEntityFrameworkScope<TKey>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
ArgumentNullException.ThrowIfNull(builder);
builder.Configurations
.Add(new OpenIddictEntityFrameworkApplicationConfiguration<TApplication, TAuthorization, TToken, TKey>())
.Add(new OpenIddictEntityFrameworkAuthorizationConfiguration<TAuthorization, TApplication, TToken, TKey>())
.Add(new OpenIddictEntityFrameworkApplicationConfiguration<TApplication, TAuthorization, TSession, TToken, TKey>())
.Add(new OpenIddictEntityFrameworkAuthorizationConfiguration<TAuthorization, TApplication, TSession, TToken, TKey>())
.Add(new OpenIddictEntityFrameworkResourceConfiguration<TResource, TKey>())
.Add(new OpenIddictEntityFrameworkScopeConfiguration<TScope, TKey>())
.Add(new OpenIddictEntityFrameworkSessionConfiguration<TSession, TApplication, TAuthorization, TToken, TKey>())
.Add(new OpenIddictEntityFrameworkTokenConfiguration<TToken, TApplication, TAuthorization, TKey>());
.Add(new OpenIddictEntityFrameworkTokenConfiguration<TToken, TApplication, TAuthorization, TSession, TKey>());
return builder;
}

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

@ -28,6 +28,7 @@ namespace OpenIddict.EntityFramework;
public class OpenIddictEntityFrameworkApplicationStore :
OpenIddictEntityFrameworkApplicationStore<OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkSession,
OpenIddictEntityFrameworkToken, string>
{
public OpenIddictEntityFrameworkApplicationStore(
@ -44,16 +45,19 @@ public class OpenIddictEntityFrameworkApplicationStore :
/// </summary>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkApplicationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkApplicationStore(
@ -120,17 +124,6 @@ public class OpenIddictEntityFrameworkApplicationStore<
var context = await Context.GetDbContextAsync(cancellationToken);
Task<List<TAuthorization>> ListAuthorizationsAsync()
=> (from authorization in context.Set<TAuthorization>().Include(static authorization => authorization.Tokens)
where authorization.Application!.Id!.Equals(application.Id)
select authorization).ToListAsync(cancellationToken);
Task<List<TToken>> ListTokensAsync()
=> (from token in context.Set<TToken>()
where token.Authorization == null
where token.Application!.Id!.Equals(application.Id)
select token).ToListAsync(cancellationToken);
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
@ -138,7 +131,11 @@ public class OpenIddictEntityFrameworkApplicationStore<
// Remove all the authorizations associated with the application and
// the tokens attached to these implicit or explicit authorizations.
var authorizations = await ListAuthorizationsAsync();
var authorizations = await
(from authorization in context.Set<TAuthorization>().Include(static authorization => authorization.Tokens)
where authorization.Application!.Id!.Equals(application.Id)
select authorization).ToListAsync(cancellationToken);
foreach (var authorization in authorizations)
{
foreach (var token in authorization.Tokens)
@ -149,8 +146,42 @@ public class OpenIddictEntityFrameworkApplicationStore<
context.Set<TAuthorization>().Remove(authorization);
}
// Remove all the sessions associated with the application, the authorizations associated
// with the session and the tokens attached to these authorizations and sessions.
var sessions = await
(from session in context.Set<TSession>()
.Include(static session => session.Authorization!.Tokens)
.Include(static session => session.Tokens)
where session.Application!.Id!.Equals(application.Id)
select session).ToListAsync(cancellationToken);
foreach (var session in sessions)
{
if (session.Authorization is not null)
{
foreach (var token in session.Authorization.Tokens)
{
context.Set<TToken>().Remove(token);
}
context.Set<TAuthorization>().Remove(session.Authorization);
}
foreach (var token in session.Tokens)
{
context.Set<TToken>().Remove(token);
}
context.Set<TSession>().Remove(session);
}
// Remove all the tokens associated with the application.
var tokens = await ListTokensAsync();
var tokens = await
(from token in context.Set<TToken>()
where token.Authorization == null
where token.Application!.Id!.Equals(application.Id)
select token).ToListAsync(cancellationToken);
foreach (var token in tokens)
{
context.Set<TToken>().Remove(token);
@ -166,7 +197,7 @@ public class OpenIddictEntityFrameworkApplicationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(application).State = EntityState.Unchanged;
foreach (var authorization in authorizations)
@ -179,6 +210,26 @@ public class OpenIddictEntityFrameworkApplicationStore<
}
}
foreach (var session in sessions)
{
context.Entry(session).State = EntityState.Unchanged;
if (session.Authorization is not null)
{
context.Entry(session.Authorization).State = EntityState.Unchanged;
foreach (var token in session.Authorization.Tokens)
{
context.Entry(token).State = EntityState.Unchanged;
}
}
foreach (var token in session.Tokens)
{
context.Entry(token).State = EntityState.Unchanged;
}
}
foreach (var token in tokens)
{
context.Entry(token).State = EntityState.Unchanged;
@ -1043,7 +1094,7 @@ public class OpenIddictEntityFrameworkApplicationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(application).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

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

@ -26,6 +26,7 @@ namespace OpenIddict.EntityFramework;
public class OpenIddictEntityFrameworkAuthorizationStore :
OpenIddictEntityFrameworkAuthorizationStore<OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkSession,
OpenIddictEntityFrameworkToken, string>
{
public OpenIddictEntityFrameworkAuthorizationStore(
@ -42,16 +43,19 @@ public class OpenIddictEntityFrameworkAuthorizationStore :
/// </summary>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkAuthorizationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkAuthorizationStore(
@ -118,23 +122,49 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
var context = await Context.GetDbContextAsync(cancellationToken);
Task<List<TToken>> ListTokensAsync()
=> (from token in context.Set<TToken>()
where token.Authorization!.Id!.Equals(authorization.Id)
select token).ToListAsync(cancellationToken);
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
using var transaction = CreateTransaction(context, IsolationLevel.Serializable);
// Remove all the tokens associated with the authorization.
var tokens = await ListTokensAsync();
var tokens = await
(from token in context.Set<TToken>()
where token.Authorization!.Id!.Equals(authorization.Id)
select token).ToListAsync(cancellationToken);
foreach (var token in tokens)
{
context.Set<TToken>().Remove(token);
}
// Remove all the sessions associated with the authorization
// and the tokens attached to these authorizations and sessions.
var sessions = await
(from session in context.Set<TSession>().Include(static session => session.Tokens)
where session.Authorization!.Id!.Equals(authorization.Id)
select session).ToListAsync(cancellationToken);
foreach (var session in sessions)
{
if (session.Authorization is not null)
{
foreach (var token in session.Authorization.Tokens)
{
context.Set<TToken>().Remove(token);
}
context.Set<TAuthorization>().Remove(session.Authorization);
}
foreach (var token in session.Tokens)
{
context.Set<TToken>().Remove(token);
}
context.Set<TSession>().Remove(session);
}
context.Set<TAuthorization>().Remove(authorization);
try
@ -145,9 +175,29 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
foreach (var session in sessions)
{
context.Entry(session).State = EntityState.Unchanged;
if (session.Authorization is not null)
{
context.Entry(session.Authorization).State = EntityState.Unchanged;
foreach (var token in session.Authorization.Tokens)
{
context.Entry(token).State = EntityState.Unchanged;
}
}
foreach (var token in session.Tokens)
{
context.Entry(token).State = EntityState.Unchanged;
}
}
foreach (var token in tokens)
{
context.Entry(token).State = EntityState.Unchanged;
@ -554,7 +604,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0243), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -605,7 +655,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -619,7 +669,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -651,7 +701,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -665,7 +715,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -696,7 +746,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -710,7 +760,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -885,7 +935,7 @@ public class OpenIddictEntityFrameworkAuthorizationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

4
src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkResourceStore.cs

@ -119,7 +119,7 @@ public class OpenIddictEntityFrameworkResourceStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(resource).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -561,7 +561,7 @@ public class OpenIddictEntityFrameworkResourceStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(resource).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

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

@ -119,7 +119,7 @@ public class OpenIddictEntityFrameworkScopeStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(scope).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -667,7 +667,7 @@ public class OpenIddictEntityFrameworkScopeStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(scope).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

139
src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkSessionStore.cs

@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
@ -51,10 +52,10 @@ public class OpenIddictEntityFrameworkSessionStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictSessionStore<TSession>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkSessionStore(
@ -121,6 +122,23 @@ public class OpenIddictEntityFrameworkSessionStore<
var context = await Context.GetDbContextAsync(cancellationToken);
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
using var transaction = CreateTransaction(context, IsolationLevel.Serializable);
// Remove all the tokens associated with the session.
var tokens = await
(from token in context.Set<TToken>()
where token.Authorization == null
where token.Session!.Id!.Equals(session.Id)
select token).ToListAsync(cancellationToken);
foreach (var token in tokens)
{
context.Set<TToken>().Remove(token);
}
context.Set<TSession>().Remove(session);
try
@ -130,16 +148,21 @@ public class OpenIddictEntityFrameworkSessionStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(session).State = EntityState.Unchanged;
foreach (var token in tokens)
{
context.Entry(token).State = EntityState.Unchanged;
}
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
}
}
/// <inheritdoc/>
public virtual async IAsyncEnumerable<TSession> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query,
(string? Subject, string? LoginId, string? ApplicationId, string? AuthorizationId, string? Status) query,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
@ -159,6 +182,12 @@ public class OpenIddictEntityFrameworkSessionStore<
sessions = sessions.Where(session => session.Application!.Id!.Equals(key));
}
if (!string.IsNullOrEmpty(query.AuthorizationId))
{
var key = ConvertIdentifierFromString(query.AuthorizationId);
sessions = sessions.Where(session => session.Authorization!.Id!.Equals(key));
}
if (!string.IsNullOrEmpty(query.LoginId))
{
sessions = sessions.Where(session => session.LoginId == query.LoginId);
@ -495,6 +524,81 @@ public class OpenIddictEntityFrameworkSessionStore<
}
}
/// <inheritdoc/>
public virtual async ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
// Note: Entity Framework 6.x doesn't support set-based deletes, which prevents removing
// entities in a single command without having to retrieve and materialize them first.
// To work around this limitation, entities are manually listed and deleted using a batch logic.
List<Exception>? exceptions = null;
var result = 0L;
// Note: to avoid sending too many queries, the maximum number of elements
// that can be removed by a single call to PruneAsync() is deliberately limited.
for (var index = 0; index < 1_000; index++)
{
cancellationToken.ThrowIfCancellationRequested();
// To prevent concurrency exceptions from being thrown if an entry is modified
// after it was retrieved from the database, the following logic is executed in
// a repeatable read transaction, that will put a lock on the retrieved entries
// and thus prevent them from being concurrently modified outside this block.
using var transaction = CreateTransaction(context, IsolationLevel.RepeatableRead);
// Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable
// to create a SQL query with an expression calling DateTimeOffset.UtcDateTime.
// To work around this limitation, the threshold represented as a DateTimeOffset
// instance is manually converted to a UTC DateTime instance outside the query.
var date = threshold.UtcDateTime;
var sessions =
await (from session in context.Set<TSession>().Include(static session => session.Tokens)
where session.CreationDate < date
where session.Status != Statuses.Valid
where !session.Tokens.Any()
orderby session.Id
select session).Take(1_000).ToListAsync(cancellationToken);
if (sessions.Count is 0)
{
break;
}
// Note: new tokens may be attached after the sessions were retrieved
// from the database since the transaction level is deliberately limited to
// repeatable read instead of serializable for performance reasons). In this
// case, the operation will fail, which is considered an acceptable risk.
context.Set<TSession>().RemoveRange(sessions);
try
{
await context.SaveChangesAsync(cancellationToken);
transaction?.Commit();
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
exceptions ??= new List<Exception>(capacity: 1);
exceptions.Add(exception);
continue;
}
result += sessions.Count;
}
if (exceptions is { Count: > 0 })
{
throw new AggregateException(exceptions);
}
return result;
}
/// <inheritdoc/>
public virtual async ValueTask SetApplicationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
@ -661,7 +765,7 @@ public class OpenIddictEntityFrameworkSessionStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(session).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -723,4 +827,25 @@ public class OpenIddictEntityFrameworkSessionStore<
return converter.ConvertToInvariantString(identifier);
}
/// <summary>
/// Tries to create a new <see cref="DbContextTransaction"/> with the specified <paramref name="level"/>.
/// </summary>
/// <param name="context">The Entity Framework context.</param>
/// <param name="level">The desired level of isolation.</param>
/// <returns>The <see cref="DbContextTransaction"/> if it could be created, <see langword="null"/> otherwise.</returns>
protected virtual DbContextTransaction? CreateTransaction(DbContext context, IsolationLevel level)
{
ArgumentNullException.ThrowIfNull(context);
try
{
return context.Database.BeginTransaction(level);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
return null;
}
}
}

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

@ -26,7 +26,8 @@ namespace OpenIddict.EntityFramework;
public class OpenIddictEntityFrameworkTokenStore :
OpenIddictEntityFrameworkTokenStore<OpenIddictEntityFrameworkToken,
OpenIddictEntityFrameworkApplication,
OpenIddictEntityFrameworkAuthorization, string>
OpenIddictEntityFrameworkAuthorization,
OpenIddictEntityFrameworkSession, string>
{
public OpenIddictEntityFrameworkTokenStore(
IMemoryCache cache,
@ -43,15 +44,18 @@ public class OpenIddictEntityFrameworkTokenStore :
/// <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="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkTokenStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkToken<TKey, TApplication, TAuthorization, TSession>
where TApplication : OpenIddictEntityFrameworkApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkAuthorization<TKey, TApplication, TSession, TToken>
where TSession : OpenIddictEntityFrameworkSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkTokenStore(
@ -127,7 +131,7 @@ public class OpenIddictEntityFrameworkTokenStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -591,7 +595,7 @@ public class OpenIddictEntityFrameworkTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -643,7 +647,7 @@ public class OpenIddictEntityFrameworkTokenStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -657,7 +661,7 @@ public class OpenIddictEntityFrameworkTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -691,7 +695,7 @@ public class OpenIddictEntityFrameworkTokenStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -705,7 +709,7 @@ public class OpenIddictEntityFrameworkTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -739,7 +743,7 @@ public class OpenIddictEntityFrameworkTokenStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -753,7 +757,7 @@ public class OpenIddictEntityFrameworkTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -786,7 +790,7 @@ public class OpenIddictEntityFrameworkTokenStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -800,7 +804,7 @@ public class OpenIddictEntityFrameworkTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -1012,7 +1016,7 @@ public class OpenIddictEntityFrameworkTokenStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

10
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreApplication.cs

@ -16,6 +16,7 @@ namespace OpenIddict.EntityFrameworkCore.Models;
public class OpenIddictEntityFrameworkCoreApplication :
OpenIddictEntityFrameworkCoreApplication<string,
OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreSession,
OpenIddictEntityFrameworkCoreToken>
{
public OpenIddictEntityFrameworkCoreApplication() => Id = Guid.NewGuid().ToString();
@ -27,6 +28,7 @@ public class OpenIddictEntityFrameworkCoreApplication :
public class OpenIddictEntityFrameworkCoreApplication<TKey> :
OpenIddictEntityFrameworkCoreApplication<TKey,
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreSession<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>>
where TKey : notnull, IEquatable<TKey>;
@ -34,9 +36,10 @@ public class OpenIddictEntityFrameworkCoreApplication<TKey> :
/// Represents an OpenIddict application.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; ClientId = {ClientId,nq} ; ClientType = {ClientType,nq}")]
public class OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
public class OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TKey : notnull, IEquatable<TKey>
where TAuthorization : class
where TSession : class
where TToken : class
{
/// <summary>
@ -136,6 +139,11 @@ public class OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TTok
/// </summary>
public virtual IDictionary<string, string>? Settings { get; set; }
/// <summary>
/// Gets the list of the sessions associated with the application.
/// </summary>
public virtual ICollection<TSession> Sessions { get; } = new HashSet<TSession>();
/// <summary>
/// Gets the list of the tokens associated with the application.
/// </summary>

12
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreAuthorization.cs

@ -15,6 +15,7 @@ namespace OpenIddict.EntityFrameworkCore.Models;
public class OpenIddictEntityFrameworkCoreAuthorization :
OpenIddictEntityFrameworkCoreAuthorization<string,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreSession,
OpenIddictEntityFrameworkCoreToken>
{
public OpenIddictEntityFrameworkCoreAuthorization() => Id = Guid.NewGuid().ToString();
@ -26,6 +27,7 @@ public class OpenIddictEntityFrameworkCoreAuthorization :
public class OpenIddictEntityFrameworkCoreAuthorization<TKey> :
OpenIddictEntityFrameworkCoreAuthorization<TKey,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreSession<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>>
where TKey : notnull, IEquatable<TKey>;
@ -33,13 +35,14 @@ public class OpenIddictEntityFrameworkCoreAuthorization<TKey> :
/// Represents an OpenIddict authorization.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")]
public class OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
public class OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TKey : notnull, IEquatable<TKey>
where TApplication : class
where TSession : class
where TToken : class
{
/// <summary>
/// Gets or sets the application of the authorization.
/// Gets or sets the application associated with the authorization.
/// </summary>
public virtual TApplication? Application { get; set; }
@ -68,6 +71,11 @@ public class OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TTok
/// </summary>
public virtual string[]? Scopes { get; set; }
/// <summary>
/// Gets the list of the sessions associated with the authorization.
/// </summary>
public virtual ICollection<TSession> Sessions { get; } = new HashSet<TSession>();
/// <summary>
/// Gets or sets the status of the authorization.
/// </summary>

14
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreSession.cs

@ -15,7 +15,8 @@ namespace OpenIddict.EntityFrameworkCore.Models;
public class OpenIddictEntityFrameworkCoreSession :
OpenIddictEntityFrameworkCoreSession<string,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreAuthorization>
OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreToken>
{
public OpenIddictEntityFrameworkCoreSession() => Id = Guid.NewGuid().ToString();
}
@ -26,17 +27,19 @@ public class OpenIddictEntityFrameworkCoreSession :
public class OpenIddictEntityFrameworkCoreSession<TKey> :
OpenIddictEntityFrameworkCoreSession<TKey,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>>
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>>
where TKey : notnull, IEquatable<TKey>;
/// <summary>
/// Represents an OpenIddict session.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; LoginId = {LoginId,nq} ; Status = {Status,nq}")]
public class OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
public class OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
where TApplication : class
where TAuthorization : class
where TToken : class
{
/// <summary>
/// Gets or sets the application associated with the session.
@ -82,4 +85,9 @@ public class OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthoriza
/// Gets or sets the subject of the session.
/// </summary>
public virtual string? Subject { get; set; }
/// <summary>
/// Gets the list of the tokens associated with the session.
/// </summary>
public virtual ICollection<TToken> Tokens { get; } = new HashSet<TToken>();
}

14
src/OpenIddict.EntityFrameworkCore.Models/OpenIddictEntityFrameworkCoreToken.cs

@ -15,7 +15,8 @@ namespace OpenIddict.EntityFrameworkCore.Models;
public class OpenIddictEntityFrameworkCoreToken :
OpenIddictEntityFrameworkCoreToken<string,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreAuthorization>
OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreSession>
{
public OpenIddictEntityFrameworkCoreToken() => Id = Guid.NewGuid().ToString();
}
@ -26,17 +27,19 @@ public class OpenIddictEntityFrameworkCoreToken :
public class OpenIddictEntityFrameworkCoreToken<TKey> :
OpenIddictEntityFrameworkCoreToken<TKey,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>>
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreSession<TKey>>
where TKey : notnull, IEquatable<TKey>;
/// <summary>
/// Represents an OpenIddict token.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")]
public class OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
public class OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
where TApplication : class
where TAuthorization : class
where TSession : class
{
/// <summary>
/// Gets or sets the application associated with the token.
@ -96,6 +99,11 @@ public class OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorizati
/// </remarks>
public virtual string? ReferenceId { get; set; }
/// <summary>
/// Gets or sets the session associated with the token.
/// </summary>
public virtual TSession? Session { get; set; }
/// <summary>
/// Gets or sets the status of the token.
/// </summary>

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

@ -19,17 +19,20 @@ namespace OpenIddict.EntityFrameworkCore;
/// </summary>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkCoreApplicationConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IEntityTypeConfiguration<TApplication>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
public void Configure(EntityTypeBuilder<TApplication> builder)
@ -99,6 +102,11 @@ public sealed class OpenIddictEntityFrameworkCoreApplicationConfiguration<
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringString),
CreateDictionaryComparer<string>());
builder.HasMany(static application => application.Sessions)
.WithOne(static session => session.Application!)
.HasForeignKey(nameof(OpenIddictEntityFrameworkCoreSession.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id))
.IsRequired(required: false);
builder.HasMany(static application => application.Tokens)
.WithOne(static token => token.Application!)
.HasForeignKey(nameof(OpenIddictEntityFrameworkCoreToken.Application) + nameof(OpenIddictEntityFrameworkCoreApplication.Id))

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

@ -18,17 +18,20 @@ namespace OpenIddict.EntityFrameworkCore;
/// </summary>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkCoreAuthorizationConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IEntityTypeConfiguration<TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
public void Configure(EntityTypeBuilder<TAuthorization> builder)
@ -66,6 +69,12 @@ public sealed class OpenIddictEntityFrameworkCoreAuthorizationConfiguration<
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement),
CreateDictionaryComparer<JsonElement>());
builder.HasMany(static authorization => authorization.Sessions)
.WithOne(static session => session.Authorization!)
.HasForeignKey(nameof(OpenIddictEntityFrameworkCoreSession.Authorization) +
nameof(OpenIddictEntityFrameworkCoreAuthorization.Id))
.IsRequired(required: false);
builder.Property(static authorization => authorization.Status)
.HasMaxLength(50);

14
src/OpenIddict.EntityFrameworkCore/Configurations/OpenIddictEntityFrameworkCoreSessionConfiguration.cs

@ -28,10 +28,10 @@ public sealed class OpenIddictEntityFrameworkCoreSessionConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IEntityTypeConfiguration<TSession>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
public void Configure(EntityTypeBuilder<TSession> builder)
@ -74,6 +74,12 @@ public sealed class OpenIddictEntityFrameworkCoreSessionConfiguration<
static value => JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.IDictionaryStringJsonElement),
CreateDictionaryComparer<JsonElement>());
builder.HasMany(static session => session.Tokens)
.WithOne(static token => token.Session!)
.HasForeignKey(nameof(OpenIddictEntityFrameworkCoreToken.Session) +
nameof(OpenIddictEntityFrameworkCoreSession.Id))
.IsRequired(required: false);
builder.ToTable("OpenIddictSessions");
static ValueComparer CreateDictionaryComparer<TValue>() => new ValueComparer<IDictionary<string, TValue>>(

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

@ -19,16 +19,19 @@ namespace OpenIddict.EntityFrameworkCore;
/// <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="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TKey">The type of the primary key.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenIddictEntityFrameworkCoreTokenConfiguration<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IEntityTypeConfiguration<TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
{
public void Configure(EntityTypeBuilder<TToken> builder)

14
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreBuilder.cs

@ -93,12 +93,12 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>()
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
// If the specified key type isn't a string (which is special-cased by the stores to avoid having to resolve
@ -123,9 +123,9 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
provider.GetRequiredService<OpenIddictTokenManager<TToken>>()));
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictApplicationStore<TApplication>,
OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthorization, TToken, TKey>>());
OpenIddictEntityFrameworkCoreApplicationStore<TApplication, TAuthorization, TSession, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictAuthorizationStore<TAuthorization>,
OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TApplication, TToken, TKey>>());
OpenIddictEntityFrameworkCoreAuthorizationStore<TAuthorization, TApplication, TSession, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictResourceStore<TResource>,
OpenIddictEntityFrameworkCoreResourceStore<TResource, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictScopeStore<TScope>,
@ -133,7 +133,7 @@ public sealed class OpenIddictEntityFrameworkCoreBuilder
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictSessionStore<TSession>,
OpenIddictEntityFrameworkCoreSessionStore<TSession, TApplication, TAuthorization, TToken, TKey>>());
Services.Replace(ServiceDescriptor.Scoped<IOpenIddictTokenStore<TToken>,
OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuthorization, TKey>>());
OpenIddictEntityFrameworkCoreTokenStore<TToken, TApplication, TAuthorization, TSession, TKey>>());
return this;
}

8
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreCustomizer.cs

@ -23,12 +23,12 @@ public sealed class OpenIddictEntityFrameworkCoreCustomizer<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : RelationalModelCustomizer
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreCustomizer(ModelCustomizerDependencies dependencies)

30
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs

@ -102,12 +102,12 @@ public static class OpenIddictEntityFrameworkCoreHelpers
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(
this DbContextOptionsBuilder builder)
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
ArgumentNullException.ThrowIfNull(builder);
@ -142,12 +142,12 @@ public static class OpenIddictEntityFrameworkCoreHelpers
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey,
TContext>(
this DbContextOptionsBuilder<TContext> builder)
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
where TContext : DbContext
{
@ -206,22 +206,22 @@ public static class OpenIddictEntityFrameworkCoreHelpers
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey>(this ModelBuilder builder)
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TResource : OpenIddictEntityFrameworkCoreResource<TKey>
where TScope : OpenIddictEntityFrameworkCoreScope<TKey>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
ArgumentNullException.ThrowIfNull(builder);
return builder
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreApplicationConfiguration<TApplication, TAuthorization, TToken, TKey>())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreAuthorizationConfiguration<TAuthorization, TApplication, TToken, TKey>())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreApplicationConfiguration<TApplication, TAuthorization, TSession, TToken, TKey>())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreAuthorizationConfiguration<TAuthorization, TApplication, TSession, TToken, TKey>())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreResourceConfiguration<TResource, TKey>())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreScopeConfiguration<TScope, TKey>())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreSessionConfiguration<TSession, TApplication, TAuthorization, TToken, TKey>())
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreTokenConfiguration<TToken, TApplication, TAuthorization, TKey>());
.ApplyConfiguration(new OpenIddictEntityFrameworkCoreTokenConfiguration<TToken, TApplication, TAuthorization, TSession, TKey>());
}
}

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

@ -24,6 +24,7 @@ namespace OpenIddict.EntityFrameworkCore;
public class OpenIddictEntityFrameworkCoreApplicationStore :
OpenIddictEntityFrameworkCoreApplicationStore<OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreSession,
OpenIddictEntityFrameworkCoreToken, string>
{
public OpenIddictEntityFrameworkCoreApplicationStore(
@ -42,6 +43,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> :
OpenIddictEntityFrameworkCoreApplicationStore<OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreSession<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>, TKey>
where TKey : notnull, IEquatable<TKey>
{
@ -58,16 +60,19 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
/// </summary>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreApplicationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreApplicationStore(
@ -142,12 +147,16 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
where token.Application!.Id!.Equals(application.Id)
select token).ExecuteDeleteAsync(cancellationToken);
// Remove all the authorizations associated with the application and
// the tokens attached to these implicit or explicit authorizations.
// Remove all the authorizations associated with the application.
await (from authorization in context.Set<TAuthorization>()
where authorization.Application!.Id!.Equals(application.Id)
select authorization).ExecuteDeleteAsync(cancellationToken);
// Remove all the sessions associated with the application.
await (from session in context.Set<TSession>()
where session.Application!.Id!.Equals(application.Id)
select session).ExecuteDeleteAsync(cancellationToken);
// Note: calling DbContext.SaveChangesAsync() is not necessary
// with bulk delete operations as they are executed immediately.
@ -165,7 +174,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(application).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -203,6 +212,36 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
context.Remove(authorization);
}
// Remove all the sessions associated with the application, the authorizations associated
// with the session and the tokens attached to these authorizations and sessions.
var sessions = await
(from session in context.Set<TSession>()
.Include(static session => session.Authorization!.Tokens)
.Include(static session => session.Tokens)
.AsTracking()
where session.Application!.Id!.Equals(application.Id)
select session).ToListAsync(cancellationToken);
foreach (var session in sessions)
{
if (session.Authorization is not null)
{
foreach (var token in session.Authorization.Tokens)
{
context.Remove(token);
}
context.Remove(session.Authorization);
}
foreach (var token in session.Tokens)
{
context.Remove(token);
}
context.Remove(session);
}
// Remove all the tokens associated with the application.
var tokens = await (
from token in context.Set<TToken>().AsTracking()
@ -229,7 +268,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(application).State = EntityState.Unchanged;
foreach (var authorization in authorizations)
@ -242,6 +281,26 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
}
}
foreach (var session in sessions)
{
context.Entry(session).State = EntityState.Unchanged;
if (session.Authorization is not null)
{
context.Entry(session.Authorization).State = EntityState.Unchanged;
foreach (var token in session.Authorization.Tokens)
{
context.Entry(token).State = EntityState.Unchanged;
}
}
foreach (var token in session.Tokens)
{
context.Entry(token).State = EntityState.Unchanged;
}
}
foreach (var token in tokens)
{
context.Entry(token).State = EntityState.Unchanged;
@ -691,7 +750,7 @@ public class OpenIddictEntityFrameworkCoreApplicationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(application).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

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

@ -22,6 +22,7 @@ namespace OpenIddict.EntityFrameworkCore;
public class OpenIddictEntityFrameworkCoreAuthorizationStore :
OpenIddictEntityFrameworkCoreAuthorizationStore<OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreSession,
OpenIddictEntityFrameworkCoreToken, string>
{
public OpenIddictEntityFrameworkCoreAuthorizationStore(
@ -40,6 +41,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> :
OpenIddictEntityFrameworkCoreAuthorizationStore<OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreSession<TKey>,
OpenIddictEntityFrameworkCoreToken<TKey>, TKey>
where TKey : notnull, IEquatable<TKey>
{
@ -56,16 +58,19 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
/// </summary>
/// <typeparam name="TAuthorization">The type of the authorization entity.</typeparam>
/// <typeparam name="TApplication">The type of the application entity.</typeparam>
/// <typeparam name="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TToken">The type of the token entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreAuthorizationStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreAuthorizationStore(
@ -158,7 +163,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -187,6 +192,33 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
context.Remove(token);
}
// Remove all the sessions associated with the authorization
// and the tokens attached to these authorizations and sessions.
var sessions = await
(from session in context.Set<TSession>().Include(static session => session.Tokens).AsTracking()
where session.Authorization!.Id!.Equals(authorization.Id)
select session).ToListAsync(cancellationToken);
foreach (var session in sessions)
{
if (session.Authorization is not null)
{
foreach (var token in session.Authorization.Tokens)
{
context.Remove(token);
}
context.Remove(session.Authorization);
}
foreach (var token in session.Tokens)
{
context.Remove(token);
}
context.Remove(session);
}
context.Remove(authorization);
try
@ -201,7 +233,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
foreach (var token in tokens)
@ -594,7 +626,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0243), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -655,7 +687,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -669,7 +701,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -714,7 +746,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -728,7 +760,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -772,7 +804,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -786,7 +818,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -915,7 +947,7 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(authorization).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

4
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreResourceStore.cs

@ -124,7 +124,7 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(resource).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -406,7 +406,7 @@ public class OpenIddictEntityFrameworkCoreResourceStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(resource).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

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

@ -124,7 +124,7 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(scope).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -444,7 +444,7 @@ public class OpenIddictEntityFrameworkCoreScopeStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(scope).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

190
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreSessionStore.cs

@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json;
@ -66,10 +67,10 @@ public class OpenIddictEntityFrameworkCoreSessionStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictSessionStore<TSession>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreSessionStore(
@ -129,6 +130,24 @@ public class OpenIddictEntityFrameworkCoreSessionStore<
var context = await Context.GetDbContextAsync(cancellationToken);
// To prevent an SQL exception from being thrown if a new associated entity is
// created after the existing entries have been listed, the following logic is
// executed in a serializable transaction, that will lock the affected tables.
using var transaction = await CreateTransactionAsync(context,
IsolationLevel.Serializable, cancellationToken);
// Remove all the tokens associated with the session.
var tokens = await
(from token in context.Set<TToken>()
where token.Authorization == null
where token.Session!.Id!.Equals(session.Id)
select token).ToListAsync(cancellationToken);
foreach (var token in tokens)
{
context.Remove(token);
}
context.Remove(session);
try
@ -138,7 +157,7 @@ public class OpenIddictEntityFrameworkCoreSessionStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(session).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -147,7 +166,7 @@ public class OpenIddictEntityFrameworkCoreSessionStore<
/// <inheritdoc/>
public virtual async IAsyncEnumerable<TSession> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query,
(string? Subject, string? LoginId, string? ApplicationId, string? AuthorizationId, string? Status) query,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
@ -168,6 +187,12 @@ public class OpenIddictEntityFrameworkCoreSessionStore<
sessions = sessions.Where(session => session.Application!.Id!.Equals(key));
}
if (!string.IsNullOrEmpty(query.AuthorizationId))
{
var key = ConvertIdentifierFromString(query.AuthorizationId);
sessions = sessions.Where(session => session.Authorization!.Id!.Equals(key));
}
if (!string.IsNullOrEmpty(query.LoginId))
{
sessions = sessions.Where(session => session.LoginId == query.LoginId);
@ -466,6 +491,124 @@ public class OpenIddictEntityFrameworkCoreSessionStore<
}
}
/// <inheritdoc/>
public virtual async ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
var context = await Context.GetDbContextAsync(cancellationToken);
List<Exception>? exceptions = null;
var result = 0L;
// Note: the Oracle MySQL provider doesn't support DateTimeOffset and is unable
// to create a SQL query with an expression calling DateTimeOffset.UtcDateTime.
// To work around this limitation, the threshold represented as a DateTimeOffset
// instance is manually converted to a UTC DateTime instance outside the query.
var date = threshold.UtcDateTime;
// Note: to avoid sending too many queries, the maximum number of elements
// that can be removed by a single call to PruneAsync() is deliberately limited.
for (var index = 0; index < 1_000; index++)
{
cancellationToken.ThrowIfCancellationRequested();
if (!Options.CurrentValue.DisableBulkOperations)
{
try
{
var count = await
(from session in context.Set<TSession>()
where session.CreationDate < date
where session.Status != Statuses.Valid
where !session.Tokens.Any()
orderby session.Id
select session).Take(1_000).ExecuteDeleteAsync(cancellationToken);
if (count is 0)
{
break;
}
// Note: calling DbContext.SaveChangesAsync() is not necessary
// with bulk delete operations as they are executed immediately.
result += count;
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
exceptions ??= new List<Exception>(capacity: 1);
exceptions.Add(exception);
}
}
else
{
var strategy = context.Database.CreateExecutionStrategy();
var count = await strategy.ExecuteAsync(async () =>
{
// To prevent concurrency exceptions from being thrown if an entry is modified
// after it was retrieved from the database, the following logic is executed in
// a repeatable read transaction, that will put a lock on the retrieved entries
// and thus prevent them from being concurrently modified outside this block.
await using var transaction = await CreateTransactionAsync(context,
IsolationLevel.RepeatableRead, cancellationToken);
var sessions = await
(from session in context.Set<TSession>()
.Include(static session => session.Tokens)
.AsTracking()
where session.CreationDate < date
where session.Status != Statuses.Valid
where !session.Tokens.Any()
orderby session.Id
select session).Take(1_000).ToListAsync(cancellationToken);
if (sessions.Count is not 0)
{
// Note: new tokens may be attached after the sessions were retrieved
// from the database since the transaction level is deliberately limited to
// repeatable read instead of serializable for performance reasons). In this
// case, the operation will fail, which is considered an acceptable risk.
context.RemoveRange(sessions);
try
{
await context.SaveChangesAsync(cancellationToken);
if (transaction is not null)
{
await transaction.CommitAsync(cancellationToken);
}
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
exceptions ??= new List<Exception>(capacity: 1);
exceptions.Add(exception);
}
}
return sessions.Count;
});
if (count is 0)
{
break;
}
result += count;
}
}
if (exceptions is { Count: > 0 })
{
throw new AggregateException(exceptions);
}
return result;
}
/// <inheritdoc/>
public virtual async ValueTask SetApplicationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{
@ -607,7 +750,7 @@ public class OpenIddictEntityFrameworkCoreSessionStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(session).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -659,4 +802,37 @@ public class OpenIddictEntityFrameworkCoreSessionStore<
return converter.ConvertToInvariantString(identifier);
}
/// <summary>
/// Tries to create a new <see cref="IDbContextTransaction"/> with the specified <paramref name="level"/>.
/// </summary>
/// <param name="context">The Entity Framework Core context.</param>
/// <param name="level">The desired level of isolation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The <see cref="IDbContextTransaction"/> if it could be created, <see langword="null"/> otherwise.</returns>
protected virtual async ValueTask<IDbContextTransaction?> CreateTransactionAsync(
DbContext context, IsolationLevel level, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
// Note: transactions that specify an explicit isolation level are only supported by
// relational providers and trying to use them with a different provider results in
// an invalid operation exception being thrown at runtime. To prevent that, a manual
// check is made to ensure the underlying transaction manager is relational.
var manager = context.GetService<IDbContextTransactionManager>();
if (manager is IRelationalTransactionManager)
{
try
{
return await context.Database.BeginTransactionAsync(level, cancellationToken);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
return null;
}
}
return null;
}
}

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

@ -22,7 +22,8 @@ namespace OpenIddict.EntityFrameworkCore;
public class OpenIddictEntityFrameworkCoreTokenStore :
OpenIddictEntityFrameworkCoreTokenStore<OpenIddictEntityFrameworkCoreToken,
OpenIddictEntityFrameworkCoreApplication,
OpenIddictEntityFrameworkCoreAuthorization, string>
OpenIddictEntityFrameworkCoreAuthorization,
OpenIddictEntityFrameworkCoreSession, string>
{
public OpenIddictEntityFrameworkCoreTokenStore(
IOpenIddictEntityFrameworkCoreContext context,
@ -40,7 +41,8 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> :
OpenIddictEntityFrameworkCoreTokenStore<OpenIddictEntityFrameworkCoreToken<TKey>,
OpenIddictEntityFrameworkCoreApplication<TKey>,
OpenIddictEntityFrameworkCoreAuthorization<TKey>, TKey>
OpenIddictEntityFrameworkCoreAuthorization<TKey>,
OpenIddictEntityFrameworkCoreSession<TKey>, TKey>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreTokenStore(
@ -57,15 +59,18 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
/// <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="TSession">The type of the session entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictEntityFrameworkCoreTokenStore<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TToken,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TApplication,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TAuthorization,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSession,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TKey> : IOpenIddictTokenStore<TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictEntityFrameworkCoreToken<TKey, TApplication, TAuthorization, TSession>
where TApplication : OpenIddictEntityFrameworkCoreApplication<TKey, TAuthorization, TSession, TToken>
where TAuthorization : OpenIddictEntityFrameworkCoreAuthorization<TKey, TApplication, TSession, TToken>
where TSession : OpenIddictEntityFrameworkCoreSession<TKey, TApplication, TAuthorization, TToken>
where TKey : notnull, IEquatable<TKey>
{
public OpenIddictEntityFrameworkCoreTokenStore(
@ -134,7 +139,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);
@ -611,7 +616,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -672,7 +677,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -686,7 +691,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -734,7 +739,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -748,7 +753,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -796,7 +801,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -810,7 +815,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -857,7 +862,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
exceptions ??= new List<Exception>(capacity: 1);
@ -871,7 +876,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
if (exceptions is { Count: > 0 })
{
throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
throw new AggregateException(exceptions);
}
return result;
@ -1058,7 +1063,7 @@ public class OpenIddictEntityFrameworkCoreTokenStore<
catch (DbUpdateConcurrencyException exception)
{
// Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
// Reset the state of the updated entities to prevents future calls from failing.
context.Entry(token).State = EntityState.Unchanged;
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239), exception);

6
src/OpenIddict.MongoDb.Models/OpenIddictMongoDbToken.cs

@ -82,6 +82,12 @@ public class OpenIddictMongoDbToken
[BsonElement("reference_id"), BsonIgnoreIfNull]
public virtual string? ReferenceId { get; set; }
/// <summary>
/// Gets or sets the identifier of the session associated with the token.
/// </summary>
[BsonElement("session_id"), BsonIgnoreIfDefault]
public virtual ObjectId SessionId { get; set; }
/// <summary>
/// Gets or sets the status of the token.
/// </summary>

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

@ -109,6 +109,10 @@ public class OpenIddictMongoDbApplicationStore<
await database.GetCollection<OpenIddictMongoDbAuthorization>(Options.CurrentValue.AuthorizationsCollectionName)
.DeleteManyAsync(authorization => authorization.ApplicationId == application.Id, cancellationToken);
// Delete the sessions associated with the application.
await database.GetCollection<OpenIddictMongoDbSession>(Options.CurrentValue.SessionsCollectionName)
.DeleteManyAsync(session => session.ApplicationId == application.Id, cancellationToken);
// Delete the tokens associated with the application.
await database.GetCollection<OpenIddictMongoDbToken>(Options.CurrentValue.TokensCollectionName)
.DeleteManyAsync(token => token.ApplicationId == application.Id, cancellationToken);

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

@ -103,6 +103,10 @@ public class OpenIddictMongoDbAuthorizationStore<
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239));
}
// Delete the sessions associated with the authorization.
await database.GetCollection<OpenIddictMongoDbSession>(Options.CurrentValue.SessionsCollectionName)
.DeleteManyAsync(session => session.AuthorizationId == authorization.Id, cancellationToken);
// Delete the tokens associated with the authorization.
await database.GetCollection<OpenIddictMongoDbToken>(Options.CurrentValue.TokensCollectionName)
.DeleteManyAsync(token => token.AuthorizationId == authorization.Id, cancellationToken);

42
src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbSessionStore.cs

@ -102,11 +102,15 @@ public class OpenIddictMongoDbSessionStore<
{
throw new ConcurrencyException(SR.GetResourceString(SR.ID0239));
}
// Delete the tokens associated with the session.
await database.GetCollection<OpenIddictMongoDbToken>(Options.CurrentValue.TokensCollectionName)
.DeleteManyAsync(token => token.SessionId == session.Id, cancellationToken);
}
/// <inheritdoc/>
public virtual async IAsyncEnumerable<TSession> FindAsync(
(string? Subject, string? LoginId, string? ApplicationId, string? Status) query,
(string? Subject, string? LoginId, string? ApplicationId, string? AuthorizationId, string? Status) query,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
@ -124,6 +128,11 @@ public class OpenIddictMongoDbSessionStore<
sessions = sessions.Where(session => session.ApplicationId == ObjectId.Parse(query.ApplicationId));
}
if (!string.IsNullOrEmpty(query.AuthorizationId))
{
sessions = sessions.Where(session => session.AuthorizationId == ObjectId.Parse(query.AuthorizationId));
}
if (!string.IsNullOrEmpty(query.LoginId))
{
sessions = sessions.Where(session => session.LoginId == query.LoginId);
@ -380,6 +389,37 @@ public class OpenIddictMongoDbSessionStore<
}
}
/// <inheritdoc/>
public virtual async ValueTask<long> PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
{
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection<TSession>(Options.CurrentValue.SessionsCollectionName);
var result = 0L;
// Note: directly deleting the resulting set of an aggregate query is not supported by MongoDB.
// To work around this limitation, the session identifiers are stored in an intermediate
// list and delete requests are sent to remove the documents corresponding to these identifiers.
var identifiers =
await (from session in collection.AsQueryable()
join token in database.GetCollection<OpenIddictMongoDbToken>(Options.CurrentValue.TokensCollectionName).AsQueryable()
on session.Id equals token.SessionId into tokens
where session.CreationDate < threshold.UtcDateTime
where session.Status != Statuses.Valid
where !tokens.Any()
select session.Id).ToListAsync(cancellationToken);
// Note: to avoid generating delete requests with very large filters, chunking is used here and the
// maximum number of elements that can be removed by a single call to PruneAsync() is deliberately limited.
foreach (var chunk in identifiers.Take(1_000_000).Chunk(1_000))
{
result += (await collection.DeleteManyAsync(session => chunk.Contains(session.Id), cancellationToken)).DeletedCount;
}
return result;
}
/// <inheritdoc/>
public virtual ValueTask SetApplicationIdAsync(TSession session, string? identifier, CancellationToken cancellationToken)
{

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

@ -423,7 +423,7 @@ public class OpenIddictMongoDbTokenStore<
where token.CreationDate < threshold.UtcDateTime
where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) ||
token.ExpirationDate < DateTime.UtcNow ||
authorizations.Any(token => token.Status != Statuses.Valid)
authorizations.Any(static authorization => authorization.Status != Statuses.Valid)
select token.Id).ToListAsync(cancellationToken);
// Note: to avoid generating delete requests with very large filters, chunking is used here and the

19
src/OpenIddict.Quartz/OpenIddictQuartzBuilder.cs

@ -62,6 +62,13 @@ public sealed class OpenIddictQuartzBuilder
public OpenIddictQuartzBuilder DisableAuthorizationPruning()
=> Configure(options => options.DisableAuthorizationPruning = true);
/// <summary>
/// Disables sessions pruning.
/// </summary>
/// <returns>The <see cref="OpenIddictQuartzBuilder"/> instance.</returns>
public OpenIddictQuartzBuilder DisableSessionPruning()
=> Configure(options => options.DisableSessionPruning = true);
/// <summary>
/// Disables tokens pruning.
/// </summary>
@ -93,6 +100,18 @@ public sealed class OpenIddictQuartzBuilder
return Configure(options => options.MinimumAuthorizationLifespan = lifespan);
}
/// <summary>
/// Sets the minimum lifespan sessions must have to be pruned.
/// </summary>
/// <param name="lifespan">The minimum lifespan sessions must have to be pruned.</param>
/// <returns>The <see cref="OpenIddictQuartzBuilder"/> instance.</returns>
public OpenIddictQuartzBuilder SetMinimumSessionLifespan(TimeSpan lifespan)
{
ArgumentOutOfRangeException.ThrowIfLessThan(lifespan, TimeSpan.FromMinutes(10));
return Configure(options => options.MinimumSessionLifespan = lifespan);
}
/// <summary>
/// Sets the minimum lifespan tokens must have to be pruned.
/// </summary>

48
src/OpenIddict.Quartz/OpenIddictQuartzJob.cs

@ -146,6 +146,54 @@ public sealed class OpenIddictQuartzJob : IJob
}
}
// Important: since sessions that still have tokens attached are never
// pruned, the tokens MUST be deleted before deleting the sessions.
if (!_options.CurrentValue.DisableSessionPruning)
{
var manager = scope.ServiceProvider.GetService<IOpenIddictSessionManager>()
?? throw new JobExecutionException(new InvalidOperationException(SR.GetResourceString(SR.ID0278)))
{
RefireImmediately = false,
UnscheduleAllTriggers = true,
UnscheduleFiringTrigger = true
};
var threshold = _options.CurrentValue.TimeProvider.GetUtcNow() - _options.CurrentValue.MinimumSessionLifespan;
try
{
await manager.PruneAsync(threshold, context.CancellationToken);
}
// OperationCanceledExceptions are typically thrown when the host is about to shut down.
// To allow the host to shut down as fast as possible, this exception type is special-cased
// to prevent further processing in this job and inform Quartz.NET it shouldn't be refired.
catch (OperationCanceledException exception) when (context.CancellationToken.IsCancellationRequested)
{
throw new JobExecutionException(exception)
{
RefireImmediately = false
};
}
// AggregateExceptions are generally thrown by the manager itself when one or multiple exception(s)
// occurred while trying to prune the entities. In this case, add the inner exceptions to the collection.
catch (AggregateException exception) when (!OpenIddictHelpers.IsFatal(exception))
{
exceptions ??= new List<Exception>(capacity: exception.InnerExceptions.Count);
exceptions.AddRange(exception.InnerExceptions);
}
// Other non-fatal exceptions are assumed to be transient and are added to the exceptions collection
// to be re-thrown later (typically, at the very end of this job, as an AggregateException).
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
exceptions ??= new List<Exception>(capacity: 1);
exceptions.Add(exception);
}
}
if (exceptions is { Count: > 0 })
{
throw new JobExecutionException(new AggregateException(exceptions))

11
src/OpenIddict.Quartz/OpenIddictQuartzOptions.cs

@ -16,6 +16,11 @@ public sealed class OpenIddictQuartzOptions
/// </summary>
public bool DisableAuthorizationPruning { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether sessions pruning should be disabled.
/// </summary>
public bool DisableSessionPruning { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether tokens pruning should be disabled.
/// </summary>
@ -33,6 +38,12 @@ public sealed class OpenIddictQuartzOptions
/// </summary>
public TimeSpan MinimumAuthorizationLifespan { get; set; } = TimeSpan.FromDays(14);
/// <summary>
/// Gets or sets the minimum lifespan sessions must have to be pruned.
/// By default, this value is set to 14 days and cannot be less than 10 minutes.
/// </summary>
public TimeSpan MinimumSessionLifespan { get; set; } = TimeSpan.FromDays(14);
/// <summary>
/// Gets or sets the minimum lifespan tokens must have to be pruned.
/// By default, this value is set to 14 days and cannot be less than 10 minutes.

1
src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs

@ -10,7 +10,6 @@ using System.Security.Claims;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Server;

1
src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs

@ -9,7 +9,6 @@ using System.Diagnostics;
using System.Security.Claims;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Server;

1
src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs

@ -13,7 +13,6 @@ using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace OpenIddict.Server;

1
src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs

@ -16,7 +16,6 @@ using System.Text;
using System.Text.Json.Nodes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

1
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -18,7 +18,6 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

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

@ -39,11 +39,11 @@ public class OpenIddictEntityFrameworkBuilderTests
Assert.Contains(services, service =>
service.Lifetime is ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictApplicationStore<CustomApplication>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkApplicationStore<CustomApplication, CustomAuthorization, CustomToken, long>));
service.ImplementationType == typeof(OpenIddictEntityFrameworkApplicationStore<CustomApplication, CustomAuthorization, CustomSession, CustomToken, long>));
Assert.Contains(services, service =>
service.Lifetime is ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictAuthorizationStore<CustomAuthorization>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkAuthorizationStore<CustomAuthorization, CustomApplication, CustomToken, long>));
service.ImplementationType == typeof(OpenIddictEntityFrameworkAuthorizationStore<CustomAuthorization, CustomApplication, CustomSession, CustomToken, long>));
Assert.Contains(services, service =>
service.Lifetime is ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictResourceStore<CustomResource>) &&
@ -59,7 +59,7 @@ public class OpenIddictEntityFrameworkBuilderTests
Assert.Contains(services, service =>
service.Lifetime is ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenStore<CustomToken>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkTokenStore<CustomToken, CustomApplication, CustomAuthorization, long>));
service.ImplementationType == typeof(OpenIddictEntityFrameworkTokenStore<CustomToken, CustomApplication, CustomAuthorization, CustomSession, long>));
}
[Fact]
@ -90,12 +90,12 @@ public class OpenIddictEntityFrameworkBuilderTests
return services;
}
public class CustomApplication : OpenIddictEntityFrameworkApplication<long, CustomAuthorization, CustomToken>;
public class CustomAuthorization : OpenIddictEntityFrameworkAuthorization<long, CustomApplication, CustomToken>;
public class CustomApplication : OpenIddictEntityFrameworkApplication<long, CustomAuthorization, CustomSession, CustomToken>;
public class CustomAuthorization : OpenIddictEntityFrameworkAuthorization<long, CustomApplication, CustomSession, CustomToken>;
public class CustomResource : OpenIddictEntityFrameworkResource<long>;
public class CustomScope : OpenIddictEntityFrameworkScope<long>;
public class CustomSession : OpenIddictEntityFrameworkSession<long, CustomApplication, CustomAuthorization>;
public class CustomToken : OpenIddictEntityFrameworkToken<long, CustomApplication, CustomAuthorization>;
public class CustomSession : OpenIddictEntityFrameworkSession<long, CustomApplication, CustomAuthorization, CustomToken>;
public class CustomToken : OpenIddictEntityFrameworkToken<long, CustomApplication, CustomAuthorization, CustomSession>;
public class CustomDbContext : DbContext
{

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

@ -39,11 +39,11 @@ public class OpenIddictEntityFrameworkCoreBuilderTests
Assert.Contains(services, service =>
service.Lifetime is ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictApplicationStore<CustomApplication>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreApplicationStore<CustomApplication, CustomAuthorization, CustomToken, long>));
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreApplicationStore<CustomApplication, CustomAuthorization, CustomSession, CustomToken, long>));
Assert.Contains(services, service =>
service.Lifetime is ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictAuthorizationStore<CustomAuthorization>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<CustomAuthorization, CustomApplication, CustomToken, long>));
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreAuthorizationStore<CustomAuthorization, CustomApplication, CustomSession, CustomToken, long>));
Assert.Contains(services, service =>
service.Lifetime is ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictResourceStore<CustomResource>) &&
@ -59,7 +59,7 @@ public class OpenIddictEntityFrameworkCoreBuilderTests
Assert.Contains(services, service =>
service.Lifetime is ServiceLifetime.Scoped &&
service.ServiceType == typeof(IOpenIddictTokenStore<CustomToken>) &&
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreTokenStore<CustomToken, CustomApplication, CustomAuthorization, long>));
service.ImplementationType == typeof(OpenIddictEntityFrameworkCoreTokenStore<CustomToken, CustomApplication, CustomAuthorization, CustomSession, long>));
}
[Fact]
@ -90,12 +90,12 @@ public class OpenIddictEntityFrameworkCoreBuilderTests
return services;
}
public class CustomApplication : OpenIddictEntityFrameworkCoreApplication<long, CustomAuthorization, CustomToken>;
public class CustomAuthorization : OpenIddictEntityFrameworkCoreAuthorization<long, CustomApplication, CustomToken>;
public class CustomApplication : OpenIddictEntityFrameworkCoreApplication<long, CustomAuthorization, CustomSession, CustomToken>;
public class CustomAuthorization : OpenIddictEntityFrameworkCoreAuthorization<long, CustomApplication, CustomSession, CustomToken>;
public class CustomResource : OpenIddictEntityFrameworkCoreResource<long>;
public class CustomScope : OpenIddictEntityFrameworkCoreScope<long>;
public class CustomSession : OpenIddictEntityFrameworkCoreSession<long, CustomApplication, CustomAuthorization>;
public class CustomToken : OpenIddictEntityFrameworkCoreToken<long, CustomApplication, CustomAuthorization>;
public class CustomSession : OpenIddictEntityFrameworkCoreSession<long, CustomApplication, CustomAuthorization, CustomToken>;
public class CustomToken : OpenIddictEntityFrameworkCoreToken<long, CustomApplication, CustomAuthorization, CustomSession>;
public class CustomDbContext : DbContext
{

26
test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictEntityFrameworkCoreHelpersTests.cs

@ -37,9 +37,9 @@ public class OpenIddictEntityFrameworkCoreHelpersTests
// Assert
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreApplicationConfiguration<OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreToken, string>>()), Times.Once());
It.IsAny<OpenIddictEntityFrameworkCoreApplicationConfiguration<OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreSession, OpenIddictEntityFrameworkCoreToken, string>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreAuthorizationConfiguration<OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreToken, string>>()), Times.Once());
It.IsAny<OpenIddictEntityFrameworkCoreAuthorizationConfiguration<OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreSession, OpenIddictEntityFrameworkCoreToken, string>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreResourceConfiguration<OpenIddictEntityFrameworkCoreResource, string>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
@ -47,7 +47,7 @@ public class OpenIddictEntityFrameworkCoreHelpersTests
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreSessionConfiguration<OpenIddictEntityFrameworkCoreSession, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreToken, string>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreTokenConfiguration<OpenIddictEntityFrameworkCoreToken, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreAuthorization, string>>()), Times.Once());
It.IsAny<OpenIddictEntityFrameworkCoreTokenConfiguration<OpenIddictEntityFrameworkCoreToken, OpenIddictEntityFrameworkCoreApplication, OpenIddictEntityFrameworkCoreAuthorization, OpenIddictEntityFrameworkCoreSession, string>>()), Times.Once());
}
[Fact]
@ -73,9 +73,9 @@ public class OpenIddictEntityFrameworkCoreHelpersTests
// Assert
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreApplicationConfiguration<OpenIddictEntityFrameworkCoreApplication<long>, OpenIddictEntityFrameworkCoreAuthorization<long>, OpenIddictEntityFrameworkCoreToken<long>, long>>()), Times.Once());
It.IsAny<OpenIddictEntityFrameworkCoreApplicationConfiguration<OpenIddictEntityFrameworkCoreApplication<long>, OpenIddictEntityFrameworkCoreAuthorization<long>, OpenIddictEntityFrameworkCoreSession<long>, OpenIddictEntityFrameworkCoreToken<long>, long>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreAuthorizationConfiguration<OpenIddictEntityFrameworkCoreAuthorization<long>, OpenIddictEntityFrameworkCoreApplication<long>, OpenIddictEntityFrameworkCoreToken<long>, long>>()), Times.Once());
It.IsAny<OpenIddictEntityFrameworkCoreAuthorizationConfiguration<OpenIddictEntityFrameworkCoreAuthorization<long>, OpenIddictEntityFrameworkCoreApplication<long>, OpenIddictEntityFrameworkCoreSession<long>, OpenIddictEntityFrameworkCoreToken<long>, long>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreResourceConfiguration<OpenIddictEntityFrameworkCoreResource<long>, long>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
@ -83,7 +83,7 @@ public class OpenIddictEntityFrameworkCoreHelpersTests
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreSessionConfiguration<OpenIddictEntityFrameworkCoreSession<long>, OpenIddictEntityFrameworkCoreApplication<long>, OpenIddictEntityFrameworkCoreAuthorization<long>, OpenIddictEntityFrameworkCoreToken<long>, long>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreTokenConfiguration<OpenIddictEntityFrameworkCoreToken<long>, OpenIddictEntityFrameworkCoreApplication<long>, OpenIddictEntityFrameworkCoreAuthorization<long>, long>>()), Times.Once());
It.IsAny<OpenIddictEntityFrameworkCoreTokenConfiguration<OpenIddictEntityFrameworkCoreToken<long>, OpenIddictEntityFrameworkCoreApplication<long>, OpenIddictEntityFrameworkCoreAuthorization<long>, OpenIddictEntityFrameworkCoreSession<long>, long>>()), Times.Once());
}
[Fact]
@ -109,9 +109,9 @@ public class OpenIddictEntityFrameworkCoreHelpersTests
// Assert
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreApplicationConfiguration<CustomApplication, CustomAuthorization, CustomToken, Guid>>()), Times.Once());
It.IsAny<OpenIddictEntityFrameworkCoreApplicationConfiguration<CustomApplication, CustomAuthorization, CustomSession, CustomToken, Guid>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreAuthorizationConfiguration<CustomAuthorization, CustomApplication, CustomToken, Guid>>()), Times.Once());
It.IsAny<OpenIddictEntityFrameworkCoreAuthorizationConfiguration<CustomAuthorization, CustomApplication, CustomSession, CustomToken, Guid>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreResourceConfiguration<CustomResource, Guid>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
@ -119,13 +119,13 @@ public class OpenIddictEntityFrameworkCoreHelpersTests
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreSessionConfiguration<CustomSession, CustomApplication, CustomAuthorization, CustomToken, Guid>>()), Times.Once());
builder.Verify(mock => mock.ApplyConfiguration(
It.IsAny<OpenIddictEntityFrameworkCoreTokenConfiguration<CustomToken, CustomApplication, CustomAuthorization, Guid>>()), Times.Once());
It.IsAny<OpenIddictEntityFrameworkCoreTokenConfiguration<CustomToken, CustomApplication, CustomAuthorization, CustomSession, Guid>>()), Times.Once());
}
public class CustomApplication : OpenIddictEntityFrameworkCoreApplication<Guid, CustomAuthorization, CustomToken>;
public class CustomAuthorization : OpenIddictEntityFrameworkCoreAuthorization<Guid, CustomApplication, CustomToken>;
public class CustomApplication : OpenIddictEntityFrameworkCoreApplication<Guid, CustomAuthorization, CustomSession, CustomToken>;
public class CustomAuthorization : OpenIddictEntityFrameworkCoreAuthorization<Guid, CustomApplication, CustomSession, CustomToken>;
public class CustomResource : OpenIddictEntityFrameworkCoreResource<Guid>;
public class CustomScope : OpenIddictEntityFrameworkCoreScope<Guid>;
public class CustomSession : OpenIddictEntityFrameworkCoreSession<Guid, CustomApplication, CustomAuthorization>;
public class CustomToken : OpenIddictEntityFrameworkCoreToken<Guid, CustomApplication, CustomAuthorization>;
public class CustomSession : OpenIddictEntityFrameworkCoreSession<Guid, CustomApplication, CustomAuthorization, CustomToken>;
public class CustomToken : OpenIddictEntityFrameworkCoreToken<Guid, CustomApplication, CustomAuthorization, CustomSession>;
}

45
test/OpenIddict.Quartz.Tests/OpenIddictQuartzBuilderTests.cs

@ -63,6 +63,22 @@ public class OpenIddictQuartzBuilderTests
Assert.True(options.DisableAuthorizationPruning);
}
[Fact]
public void DisableSessionPruning_SessionPruningIsDisabled()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.DisableSessionPruning();
var options = GetOptions(services);
// Assert
Assert.True(options.DisableSessionPruning);
}
[Fact]
public void DisableTokenPruning_TokenPruningIsDisabled()
{
@ -137,6 +153,35 @@ public class OpenIddictQuartzBuilderTests
Assert.Equal(42, options.MinimumAuthorizationLifespan.TotalDays);
}
[Fact]
public void SetMinimumSessionLifespan_ThrowsAnExceptionForNegativeLifespan()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentOutOfRangeException>(() => builder.SetMinimumSessionLifespan(TimeSpan.FromSeconds(-1)));
Assert.Equal("lifespan", exception.ParamName);
}
[Fact]
public void SetMinimumSessionLifespan_MinimumSessionLifespanIsSet()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetMinimumSessionLifespan(TimeSpan.FromDays(42));
var options = GetOptions(services);
// Assert
Assert.Equal(42, options.MinimumSessionLifespan.TotalDays);
}
[Fact]
public void SetMinimumTokenLifespan_ThrowsAnExceptionForNegativeLifespan()
{

124
test/OpenIddict.Quartz.Tests/OpenIddictQuartzJobTests.cs

@ -23,6 +23,7 @@ public class OpenIddictQuartzJobTests
// Arrange
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictSessionManager)) == Mock.Of<IOpenIddictSessionManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
var scope = Mock.Of<IServiceScope>(scope => scope.ServiceProvider == provider);
@ -52,6 +53,7 @@ public class OpenIddictQuartzJobTests
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictSessionManager)) == Mock.Of<IOpenIddictSessionManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == manager.Object);
var job = CreateJob(provider, options => options.DisableTokenPruning = true);
@ -72,6 +74,7 @@ public class OpenIddictQuartzJobTests
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object &&
provider.GetService(typeof(IOpenIddictSessionManager)) == Mock.Of<IOpenIddictSessionManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
var job = CreateJob(provider, options => options.DisableAuthorizationPruning = true);
@ -83,12 +86,34 @@ public class OpenIddictQuartzJobTests
manager.Verify(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task Execute_IgnoresPruningWhenSessionPruningIsDisabled()
{
// Arrange
var manager = new Mock<IOpenIddictSessionManager>();
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictSessionManager)) == manager.Object &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
var job = CreateJob(provider, options => options.DisableSessionPruning = true);
// Act
await job.Execute(Mock.Of<IJobExecutionContext>());
// Assert
manager.Verify(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task Execute_UnschedulesTriggersWhenTokenManagerIsMissing()
{
// Arrange
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictSessionManager)) == Mock.Of<IOpenIddictSessionManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == null);
var job = CreateJob(provider);
@ -124,6 +149,26 @@ public class OpenIddictQuartzJobTests
Assert.Equal(SR.GetResourceString(SR.ID0278), exception.InnerException!.Message);
}
[Fact]
public async Task Execute_UnschedulesTriggersWhenSessionManagerIsMissing()
{
// Arrange
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictSessionManager)) == null);
var job = CreateJob(provider);
// Act and assert
var exception = await Assert.ThrowsAsync<JobExecutionException>(() => job.Execute(Mock.Of<IJobExecutionContext>()));
Assert.False(exception.RefireImmediately);
Assert.True(exception.UnscheduleAllTriggers);
Assert.True(exception.UnscheduleFiringTrigger);
Assert.IsType<InvalidOperationException>(exception.InnerException);
Assert.Equal(SR.GetResourceString(SR.ID0278), exception.InnerException!.Message);
}
[Fact]
public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringTokenPruning()
{
@ -160,6 +205,25 @@ public class OpenIddictQuartzJobTests
await Assert.ThrowsAsync<OutOfMemoryException>(() => job.Execute(Mock.Of<IJobExecutionContext>()));
}
[Fact]
public async Task Execute_RethrowsOutOfMemoryExceptionsThrownDuringSessionPruning()
{
// Arrange
var manager = new Mock<IOpenIddictSessionManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(new OutOfMemoryException());
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>() &&
provider.GetService(typeof(IOpenIddictSessionManager)) == manager.Object);
var job = CreateJob(provider);
// Act and assert
await Assert.ThrowsAsync<OutOfMemoryException>(() => job.Execute(Mock.Of<IJobExecutionContext>()));
}
[Fact]
public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringTokenPruning()
{
@ -212,6 +276,33 @@ public class OpenIddictQuartzJobTests
manager.Verify(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task Execute_DisablesRefiringWhenJobIsCanceledDuringSessionPruning()
{
// Arrange
var token = new CancellationToken(canceled: true);
var manager = new Mock<IOpenIddictSessionManager>();
manager.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(new OperationCanceledException(token));
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == Mock.Of<IOpenIddictAuthorizationManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>() &&
provider.GetService(typeof(IOpenIddictSessionManager)) == manager.Object);
var context = Mock.Of<IJobExecutionContext>(context => context.CancellationToken == token);
var job = CreateJob(provider);
// Act and assert
var exception = await Assert.ThrowsAsync<JobExecutionException>(() => job.Execute(context));
Assert.False(exception.RefireImmediately);
manager.Verify(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task Execute_AllowsRefiringWhenExceptionsAreThrown()
{
@ -220,6 +311,9 @@ public class OpenIddictQuartzJobTests
provider.Setup(provider => provider.GetService(typeof(IOpenIddictAuthorizationManager)))
.Returns(CreateAuthorizationManager(new ApplicationException()));
provider.Setup(provider => provider.GetService(typeof(IOpenIddictSessionManager)))
.Returns(CreateSessionManager(new ApplicationException()));
provider.Setup(provider => provider.GetService(typeof(IOpenIddictTokenManager)))
.Returns(CreateTokenManager(new ApplicationException()));
@ -232,9 +326,10 @@ public class OpenIddictQuartzJobTests
Assert.True(exception.RefireImmediately);
Assert.IsType<AggregateException>(exception.InnerException);
Assert.Equal(2, ((AggregateException) exception.InnerException!).InnerExceptions.Count);
Assert.Equal(3, ((AggregateException) exception.InnerException!).InnerExceptions.Count);
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[0]);
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[1]);
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[2]);
static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception)
{
@ -245,6 +340,15 @@ public class OpenIddictQuartzJobTests
return mock.Object;
}
static IOpenIddictSessionManager CreateSessionManager(Exception exception)
{
var mock = new Mock<IOpenIddictSessionManager>();
mock.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(exception);
return mock.Object;
}
static IOpenIddictTokenManager CreateTokenManager(Exception exception)
{
var mock = new Mock<IOpenIddictTokenManager>();
@ -264,6 +368,10 @@ public class OpenIddictQuartzJobTests
.Returns(CreateAuthorizationManager(new AggregateException(
new InvalidOperationException(), new ApplicationException())));
provider.Setup(provider => provider.GetService(typeof(IOpenIddictSessionManager)))
.Returns(CreateSessionManager(new AggregateException(
new InvalidOperationException(), new ApplicationException())));
provider.Setup(provider => provider.GetService(typeof(IOpenIddictTokenManager)))
.Returns(CreateTokenManager(new AggregateException(
new InvalidOperationException(), new ApplicationException())));
@ -277,11 +385,13 @@ public class OpenIddictQuartzJobTests
Assert.True(exception.RefireImmediately);
Assert.IsType<AggregateException>(exception.InnerException);
Assert.Equal(4, ((AggregateException) exception.InnerException!).InnerExceptions.Count);
Assert.Equal(6, ((AggregateException) exception.InnerException!).InnerExceptions.Count);
Assert.IsType<InvalidOperationException>(((AggregateException) exception.InnerException!).InnerExceptions[0]);
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[1]);
Assert.IsType<InvalidOperationException>(((AggregateException) exception.InnerException!).InnerExceptions[2]);
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[3]);
Assert.IsType<InvalidOperationException>(((AggregateException) exception.InnerException!).InnerExceptions[4]);
Assert.IsType<ApplicationException>(((AggregateException) exception.InnerException!).InnerExceptions[5]);
static IOpenIddictAuthorizationManager CreateAuthorizationManager(Exception exception)
{
@ -292,6 +402,15 @@ public class OpenIddictQuartzJobTests
return mock.Object;
}
static IOpenIddictSessionManager CreateSessionManager(Exception exception)
{
var mock = new Mock<IOpenIddictSessionManager>();
mock.Setup(manager => manager.PruneAsync(It.IsAny<DateTimeOffset>(), It.IsAny<CancellationToken>()))
.Throws(exception);
return mock.Object;
}
static IOpenIddictTokenManager CreateTokenManager(Exception exception)
{
var mock = new Mock<IOpenIddictTokenManager>();
@ -312,6 +431,7 @@ public class OpenIddictQuartzJobTests
var provider = Mock.Of<IServiceProvider>(provider =>
provider.GetService(typeof(IOpenIddictAuthorizationManager)) == manager.Object &&
provider.GetService(typeof(IOpenIddictSessionManager)) == Mock.Of<IOpenIddictSessionManager>() &&
provider.GetService(typeof(IOpenIddictTokenManager)) == Mock.Of<IOpenIddictTokenManager>());
var context = Mock.Of<IJobExecutionContext>(context => context.RefireCount == 5);

Loading…
Cancel
Save